Disentangling Effects on the Viking Data
Load Data
ellipsisApply <- function(..., FUN) {
lapply(as.list(...), FUN)
}
load("LM1996-NumPoolCom-QDat-2021-05.RData")
# Stop if not all are not null
stopifnot(all(unlist(ellipsisApply(
FUN = function(bool) {!is.null(bool)},
candidateData,
islandInteractionsOneEmptyTwo,
islandInteractionsOneEmptyTwoWhich,
islandInteractionsOneTwo,
islandInteractionsOneTwoWhich,
mats,
paramFrame,
plotScalingData,
pools
))))
plotScaling <- plotly::plot_ly(
plotScalingData,
x = ~Basals,
y = ~Consumers,
z = ~CommunitySize,
color = ~Dataset,
colors = c("red", "blue", "black")
)
plotScaling <- plotly::add_markers(plotScaling)
plotScaling <- plotly::layout(
plotScaling,
scene = list(
xaxis = list(type = "log"),
yaxis = list(type = "log"),
camera = list(
eye = list(
x = -1.25, y = -1.25, z = .05
)
)
)
)
plotScaling
# Check that the Two island and Three island scenarios are set-up the same.
stopifnot(unlist(lapply(islandInteractionsOneTwo, length)) ==
unlist(lapply(islandInteractionsOneEmptyTwo, length)))
stopifnot(names(islandInteractionsOneTwo) ==
names(islandInteractionsOneEmptyTwo))
# Check that the Which versions correspond correctly.
stopifnot(
unlist(lapply(islandInteractionsOneTwoWhich, function(x) {
length(RMTRCode2::CsvRowSplit(x))
}))
== unlist(lapply(islandInteractionsOneTwo, function(x) {
# We're like onions; we have LAYERS!
lapply(x, function(y) {
lapply(y, function(z) {
sum(z > 1E-6) # How many "large" entries are there?
})})}))
)
stopifnot(
unlist(lapply(islandInteractionsOneEmptyTwoWhich, function(x) {
length(RMTRCode2::CsvRowSplit(x))
}))
== unlist(lapply(islandInteractionsOneEmptyTwo, function(x) {
# We're like onions; we have LAYERS!
lapply(x, function(y) {
lapply(y, function(z) {
sum(z > 1E-6) # How many "large" entries are there?
})})}))
)
# Hybrids
# Create a count of how many times each entry will be repeated.
communitiesAllRepeater <- 5 * unlist(lapply(islandInteractionsOneTwo, length))
# Create template.
communitiesAll <- data.frame(
CombnNum = rep(0, sum(communitiesAllRepeater)), # Should repeat all rows.
Basals = 0,
Consumers = 0,
Dataset = "",
DatasetID = 0,
Communities = "",
CommunitySize = 0,
OtherSteadyStates = 0, # To be recalculated
CommunityAbund = "",
CommunityProd = 0,
TotalID = "",
# Additional Column!, 1 for direct assembly, 0 unused.
IslandsUsed = rep(c(2,2,3,3,3), sum(communitiesAllRepeater)/5)
)
# Retrieve the rows used to make hybrids
communitiesAllProspects <- candidateData %>% dplyr::group_by(
CombnNum, Basals, Consumers, Dataset, DatasetID, TotalID
) %>% dplyr::select(
CombnNum:DatasetID, TotalID
) %>% dplyr::summarise(
Count = dplyr::n(), .groups = "keep"
) %>% dplyr::filter(
Count > 1
) %>% dplyr::select(
-Count
) %>% dplyr::arrange(
DatasetID, CombnNum
)
# Make sure that the names match.
stopifnot(communitiesAllProspects$TotalID == names(communitiesAllRepeater))
# Insert repetitions.
communitiesAll$CombnNum <- rep(communitiesAllProspects$CombnNum, communitiesAllRepeater)
communitiesAll$Basals <- rep(communitiesAllProspects$Basals, communitiesAllRepeater)
communitiesAll$Consumers <- rep(communitiesAllProspects$Consumers, communitiesAllRepeater)
communitiesAll$Dataset <- rep(communitiesAllProspects$Dataset, communitiesAllRepeater)
communitiesAll$DatasetID <- rep(communitiesAllProspects$DatasetID, communitiesAllRepeater)
communitiesAll$TotalID <- rep(communitiesAllProspects$TotalID, communitiesAllRepeater)
# To move over from the data.
# Communities = "",
# CommunityAbund = ""
communitiesAll[communitiesAll$IslandsUsed == 2, ]$Communities <-
islandInteractionsOneTwoWhich
communitiesAll[communitiesAll$IslandsUsed == 3, ]$Communities <-
islandInteractionsOneEmptyTwoWhich
communitiesAll[communitiesAll$IslandsUsed == 2, ]$CommunityAbund <-
# We're like onions; we have LAYERS!
unlist(lapply(islandInteractionsOneTwo, function(x) {
lapply(x, function(y) {
lapply(y, function(z) {
toString(z[z > 1E-6])
})
})
}))
communitiesAll[communitiesAll$IslandsUsed == 3, ]$CommunityAbund <-
unlist(lapply(islandInteractionsOneEmptyTwo, function(x) {
lapply(x, function(y) {
lapply(y, function(z) {
toString(z[z > 1E-6])
})
})
}))
# To calculate from the data.
# CommunitySize = 0, # To be calculated from Communities.
# OtherSteadyStates = 0, # To be recalculated last after filtering
# CommunityProd = 0, # To be recalculated after Abund stored.
communitiesAll$CommunitySize <- unlist(lapply(
communitiesAll$Communities, function(x) {
length(RMTRCode2::CsvRowSplit(x))
}))
for (r in 1:nrow(communitiesAll)) {
communitiesAll$CommunityProd[r] <- with(
communitiesAll[r, ],
RMTRCode2::Productivity(
Pool = pools[[DatasetID]][[CombnNum]],
InteractionMatrix = mats[[DatasetID]][[CombnNum]],
Community = Communities,
Populations = CommunityAbund
)
)
}
# Original systems
communitiesAll <- rbind(
candidateData %>% dplyr::select(
-CommunityFreq, -CommunitySeq
) %>% dplyr::mutate(
IslandsUsed = 1
),
communitiesAll
)
# Treating the Productivity like one might treat a hash,
# if two rows with the same properties are assigned the same hash,
# we only keep one.
# One decimal place might be excessive,
# but we can reflect on that if results down the line are not interesting.
# For the record though, it appears that it is a decently good approach at
# removing effectively numerical duplicates.
# Not bothering, sort of, with IslandsUsed, since many times a community is
# reproduced on varying numbers of islands.
# communitiesAll <- communitiesAll %>% dplyr::mutate(
# tempProd = round(CommunityProd, 2)
# ) %>% dplyr::distinct(
# CombnNum, Basals, Consumers, Dataset, DatasetID, TotalID,
# Communities, CommunitySize, tempProd, IslandsUsed,
# .keep_all = TRUE
# ) %>% dplyr::select(
# -tempProd
# )
communitiesAll <- communitiesAll %>% dplyr::mutate(
tempProd = round(CommunityProd, 2)
) %>% dplyr::group_by(
CombnNum, Basals, Consumers, Dataset, DatasetID, TotalID,
Communities, CommunitySize, tempProd,
) %>% dplyr::summarise(
CommunityAbund = dplyr::first(CommunityAbund),
CommunityProd = dplyr::first(CommunityProd),
IslandsUsed = toString(unique(IslandsUsed)),
.groups = "drop"
) %>% dplyr::select(
-tempProd
) %>% dplyr::group_by(
CombnNum, Basals, Consumers, Dataset, DatasetID, TotalID
) %>% dplyr::mutate(
OtherSteadyStates = dplyr::n() - 1,
Islands1 = grepl(pattern = "1", IslandsUsed, fixed = TRUE) # Will be useful
)
Persistence of Hybrid Communities
The idea is straightforward: after allowing interactions between islands, for islands that are not the same as one of the original communities, isolate the island and check to see what happens.
communitiesHybrids <- communitiesAll %>% dplyr::filter(
!Islands1
) %>% dplyr::select(-Islands1)
communitiesHybrids$AfterSepAbund <- ""
communitiesHybrids$AfterSepCommunity <- ""
communitiesHybrids$AfterSepCommunitySize <- 0
communitiesHybrids$AfterSepProduction <- 0
for (r in 1:nrow(communitiesHybrids)) {
temp <- with(
communitiesHybrids[r, ],
{
temp <- RMTRCode2::CsvRowSplit(Communities)
RMTRCode2::LawMorton1996_NumIntegration(
A = mats[[DatasetID]][[CombnNum]][temp, temp],
R = pools[[DatasetID]][[CombnNum]]$ReproductionRate[temp],
X = RMTRCode2::CsvRowSplit(CommunityAbund),
OuterTimeStepSize = 3E4,
Tolerance = 1E-6
) # retrieve the abundance over time matrix
}
)
temp <- temp[nrow(temp), -1] # choose last row, remove time column.
communitiesHybrids$AfterSepCommunity[r] <- toString(
RMTRCode2::CsvRowSplit(communitiesHybrids$Communities[r])[which(temp > 1E-6)]
)
temp <- temp[which(temp > 1E-6)] # remove microfoxes.
communitiesHybrids$AfterSepAbund[r] <- toString(temp)
communitiesHybrids$AfterSepCommunitySize[r] <- length(temp)
communitiesHybrids$AfterSepProduction[r] <- with(
communitiesHybrids[r, ],
RMTRCode2::Productivity(
Pool = pools[[DatasetID]][[CombnNum]],
InteractionMatrix = mats[[DatasetID]][[CombnNum]],
Community = AfterSepCommunity,
Populations = AfterSepAbund
)
)
}
communitiesHybrids <- communitiesHybrids %>% dplyr::mutate(
Persists = AfterSepCommunity == Communities,
ProdChange = AfterSepProduction - CommunityProd
)
So after running the dynamics for 3E4 time units (i.e. 3x the length of time the dynamics in the numerical assembly runs in between assembly steps and 1.5x the length of the island dynamics), the communities that persist are 6, 7, 8, 9, 10. Examining the communities themselves, they are all the same community, albeit with different starting points.
communitiesHybrids[communitiesHybrids$Persists, ]
An obvious follow-up question: how many of the communities that collapse do so to communities that we have not already seen?
communitiesHybrids <- communitiesHybrids %>% dplyr::mutate(
AfterSepCommunityAlreadyPresent = AfterSepCommunity %in% communitiesAll$Communities
)
sum(!communitiesHybrids$AfterSepCommunityAlreadyPresent)
[1] 12
Consolidating down to unique ending states we have the following.
communitiesHybrids[
!communitiesHybrids$AfterSepCommunityAlreadyPresent,
] %>% dplyr::distinct(CombnNum, AfterSepCommunity, .keep_all = TRUE)
We will add these new states to our catalogue of communities from the experiments. We also take the abundance after separation if the community persists to better reflect steady-state conditions.
communitiesAll <- rbind(
communitiesAll %>% dplyr::filter(
Islands1 == TRUE
) %>% dplyr::mutate(
HybridCollapse = FALSE, Persists = TRUE
),
communitiesHybrids %>% dplyr::mutate(
CommunityAbund = ifelse(Persists, AfterSepAbund, CommunityAbund),
Islands1 = FALSE, HybridCollapse = FALSE,
) %>% dplyr::select(
-AfterSepAbund, -AfterSepCommunity, -AfterSepCommunitySize,
-AfterSepProduction, -ProdChange, -AfterSepCommunityAlreadyPresent
) ,
with(
communitiesHybrids[
!communitiesHybrids$AfterSepCommunityAlreadyPresent,
] %>% dplyr::distinct(CombnNum, AfterSepCommunity, .keep_all = TRUE),
data.frame(
CombnNum = CombnNum,
Basals = Basals,
Consumers = Consumers,
Dataset = Dataset,
DatasetID = DatasetID,
TotalID = TotalID,
Communities = AfterSepCommunity,
CommunitySize = AfterSepCommunitySize,
CommunityAbund = AfterSepAbund,
CommunityProd = AfterSepProduction,
IslandsUsed = IslandsUsed,
OtherSteadyStates = 0,
Islands1 = FALSE,
HybridCollapse = TRUE,
Persists = TRUE,
stringsAsFactors = FALSE
))
)
Invadability of Hybrid Communities
Looking at a longer time scale, what happens if/when invasions resume? Do the hybrid communities that emerged retain the uninvadability of the parent communities?
This question should be straightforward as it is testing a step from the assembly process.
communitiesAll$Uninvadable <- NA
for (r in 1:nrow(communitiesAll)) {
communitiesAll$Uninvadable[r] <- with(
communitiesAll[r, ],
{
tempRow <- rep(NA, nrow(pools[[DatasetID]][[CombnNum]]) + 1)
tempRow[RMTRCode2::CsvRowSplit(Communities) + 1] <-
RMTRCode2::CsvRowSplit(CommunityAbund)
RMTRCode2::LawMorton1996_CheckUninvadable(
AbundanceRow = tempRow,
Pool = pools[[DatasetID]][[CombnNum]],
CommunityMatrix = mats[[DatasetID]][[CombnNum]]
)
}
)
}
We can compare this property against some of the other properties.
Uninvadability versus whether a community was found via assembly (“on Island 1”):
with(communitiesAll,
table(Uninvadable, Islands1))
Islands1
Uninvadable FALSE TRUE
FALSE 20 0
TRUE 32 30
Never invadable and assembled (good!), but about half of uninvadables are found without being assembled. What about of those that persist?
with(communitiesAll %>% dplyr::filter(Persists),
table(Uninvadable, Islands1))
Islands1
Uninvadable FALSE TRUE
FALSE 8 0
TRUE 0 30
Which of course fills in some of the blanks. So none of the communities that persist are uninvadable if they were not an end state of the assembly process.
Presence of Mass Effects
We check to see what happens when we treat each community as a pool for the other and perform assembly. Are the results the same as the diffusion system?
First, update the pairings.
communitiesAll <- communitiesAll %>% dplyr::group_by(
CombnNum, Basals, Consumers, Dataset, DatasetID, TotalID
) %>% dplyr::mutate(
OtherSteadyStates = dplyr::n() - 1
) %>% dplyr::ungroup()
This procedure can be done in two ways: first by directed invasion where one community is a pool for the other, and second with mutual (undirected) invasion where both communities are simultaneously pools for and invaded by each other. Note that in the directed case, we do not need to do any of the communities already marked as uninvadable with respect to the regional pools. The other communities they would be compared with are subsets of the regional pools, and so would already be checked against. We thus have matrices with three possible outcomes for entries: a set of new communities, uninvadability, or NA for unevaluated entries. In the directed case we take a row for our invader/pool and column for the invaded community, such that a community is uninvadable with respect to all other communities if its column only contains FALSE. (A community is uninvadable by itself for sake of argument.)
invasionsDirected <- list()
for (grp in unique(communitiesAll$TotalID)) {
communitiesGrp <- communitiesAll %>% dplyr::filter(TotalID == grp)
invasionsDirected[[grp]] <- matrix(
NA,
nrow = nrow(communitiesGrp),
ncol = nrow(communitiesGrp)
)
for (cl in 1:nrow(communitiesGrp)) {
if (communitiesGrp$Uninvadable[cl]) {
# No point checking, mark FALSE.
invasionsDirected[[grp]][, cl] <- FALSE
} else {
# Check to see if c(o)l(umn) is uninvadable with respect to rows.
for (r in 1:nrow(communitiesGrp)) {
if (r == cl) {invasionsDirected[[grp]][r, cl] <- FALSE; next()}
invasionsDirected[[grp]][r, cl] <- with(
communitiesGrp[cl, ],
{
tempRow <- rep(NA, nrow(pools[[DatasetID]][[CombnNum]]) + 1)
tempIDs <- RMTRCode2::CsvRowSplit(Communities)
tempRow[tempIDs + 1] <- RMTRCode2::CsvRowSplit(CommunityAbund)
# Easiest trick: set reproduction to impossible (-Inf) for species
# not in either the invaders or the invaded.
tempPool <- pools[[DatasetID]][[CombnNum]]
tempPool$ReproductionRate <- -Inf
tempPool$ReproductionRate[tempIDs] <-
pools[[DatasetID]][[CombnNum]]$ReproductionRate[tempIDs]
tempIDs <- RMTRCode2::CsvRowSplit(communitiesGrp$Communities[r])
tempPool$ReproductionRate[tempIDs] <-
pools[[DatasetID]][[CombnNum]]$ReproductionRate[tempIDs]
# Return FALSE if uninvadable, since no new communities form.
!RMTRCode2::LawMorton1996_CheckUninvadable(
AbundanceRow = tempRow,
Pool = tempPool,
CommunityMatrix = mats[[DatasetID]][[CombnNum]]
)
}
)
}
}
}
# No TRUEs (== successful invasions)? Go to next set.
if (!any(invasionsDirected[[grp]])) {next()}
# Any TRUEs are situations in which row can invade column and should be
# checked for what communities appear as a result.
for (cl in 1:nrow(communitiesGrp)) {
if (!any(invasionsDirected[[grp]][, cl])) {next()}
#TODO Develop IslandAssembly function.
}
}
Indirect Mutualism (or Competition)
Here, we check to see if the networks created by each community (hybrid or otherwise) has mutualism embedded in it.
The first obvious step is to make a gallery of the food webs. The reader will notice the upside-down ‘T’ shape to the plots.
catHeader <- function(text = "", level = 3) {
cat(paste0("\n\n",
paste(rep("#", level), collapse = ""),
" ", text, "\n"))
}
–>
We will also need to recreate code from the file LawMorton1996-NumericalTables-Parallel.Rmd.
foodWebs <- list()
for (r in 1:nrow(communitiesAll)) {
foodWebs[[r]] <- with(
communitiesAll[r, ],
{
redCom <- RMTRCode2::CsvRowSplit(Communities)
redMat <- mats[[DatasetID]][[CombnNum]][redCom, redCom]
redPool <- pools[[DatasetID]][[CombnNum]][redCom, ]
colnames(redMat) <- paste0('s',as.character(redCom))
rownames(redMat) <- colnames(redMat)
names(redPool)[1] <- "node"
redPool$node <- colnames(redMat)
names(redPool)[3] <- "M"
Graph <- igraph::graph_from_adjacency_matrix(
redMat, weighted = TRUE
)
Graph <- igraph::set.vertex.attribute(
Graph, "name", value = colnames(redMat)
)
redPool$N <- RMTRCode2::CsvRowSplit(CommunityAbund)
GraphAsDataFrame <- igraph::as_data_frame(Graph)
# cheddar does not like cannibals.
GraphAsDataFrame <- GraphAsDataFrame[
GraphAsDataFrame$to != GraphAsDataFrame$from,
]
# Add in abundances for calculating abundance * (gain or loss)
GraphAsDataFrame <- dplyr::left_join(
GraphAsDataFrame,
dplyr::select(redPool, node, N),
by = c("to" = "node")
)
# Split data frame.
ResCon <- GraphAsDataFrame[GraphAsDataFrame$weight > 0,]
ConRes <- GraphAsDataFrame[GraphAsDataFrame$weight < 0,]
# Reorder and rename variables.
ResCon <- dplyr::select(ResCon,
resource = to, consumer = from,
gainPerUnit = weight, resourceAbund = N)
ConRes <- dplyr::select(ConRes,
resource = from, consumer = to,
lossPerUnit = weight, consumerAbund = N)
ResCon <- dplyr::mutate(dplyr::group_by(ResCon, consumer),
gainEfficiency = gainPerUnit / sum(gainPerUnit),
gainActual = gainPerUnit * resourceAbund,
gainNormal = gainActual / sum(gainActual))
ConRes <- dplyr::mutate(dplyr::group_by(ConRes, resource),
lossEfficiency = lossPerUnit / sum(lossPerUnit),
lossActual = lossPerUnit * consumerAbund,
lossNormal = lossActual / sum(lossActual))
cheddarCommunity <- cheddar::Community(
redPool,
properties = list(
title = paste(TotalID, ":", Communities, ": row", r),
M.units = "masses",
N.units = "abund"
),
trophic.links = dplyr::full_join(ResCon, ConRes,
by = c("resource", "consumer"))
)
cheddarCommunity
}
)
}
Example Gallery
Closed
Example LM 1
print(cheddar::PlotWebByLevel(foodWebs[[1]], show.level.lines = TRUE,
level = "LongWeightedTrophicLevel"))
NULL

Example LM 2
print(cheddar::PlotWebByLevel(foodWebs[[28]], show.level.lines = TRUE,
level = "LongWeightedTrophicLevel"))
NULL

Invadable
print(cheddar::PlotWebByLevel(foodWebs[[41]], show.level.lines = TRUE,
level = "LongWeightedTrophicLevel"))
NULL

Does Not Persist
print(cheddar::PlotWebByLevel(foodWebs[[61]], show.level.lines = TRUE,
level = "LongWeightedTrophicLevel"))
NULL

Hybrid
print(cheddar::PlotWebByLevel(foodWebs[[81]], show.level.lines = TRUE,
level = "LongWeightedTrophicLevel"))
NULL

Measuring Indirect Interactions
Perhaps the most obvious way to measure indirect effects of one node on another is to consider the matrix power. The entries in the first power \(M^1\) represent the direct (un-normalised) effects of species \(j\) (column) on species \(i\) (row). (Multiply the interactions by the abundance column vector on the right to see why I use this convention.) Then the entries of \(M^n\) represent the effects of species \(j\) on species \(i\) after a path of exactly \(n\) steps. https://doi.org/10.1016/j.ecocom.2007.05.002 and https://doi.org/10.1111/ele.12638 both recommend essentially to normalise this score and sum it across the first so many (3 and 5 respectively) steps. The latter uses it for qualitative feeding matrices, while the former suggests biomass flow rather than the interaction matrices we are using I believe.
Some notes before we begin with this. The units are a bit wonky if we are not paying attention; the interaction matrix itself before multiplying by abundance has units inverse time-density. So instead of taking the interaction matrix \(A\) directly, we will instead take \(B:b_{i,j} = a_{i, j} x_j s\) where \(x\) is an abundance (i.e. density) and \(s\) represents a time unit. This is a bit strange, since I am not doing the obvious vector operation as I want to preserve the dimensionality. This can be thought of as integrating the matrix for one time unit instead to remove that dimension, but this makes the result invalid if the system is not in a steady-state (as the system would then have a time dependence rather than a constant integral).
Next, it is not immediately obvious (to me at least) what the correct way to measure the influence of one species on another is. I.e. should one take \(\sum_{i = 1}^{n} M^n\)? Should there be penalties with distance?
Indeed, how do we compare the effects (direct or indirect) with their influence on the system itself? That is, we can certainly calculate something, but how can we be certain that what we think we are calculating and what we are actually calculating are the same thing? For example, we would expect direct and indirect effects to be present as deviations from the steady-state are resolved, but how do we extract the indirect effects and compare with, e.g., the matrix powers?
# For each community that persists/returns to steady-state...
# Run the dynamics with a perturbation for each species in the community...
# "Integrate" (lazy Riemann sum) the dynamics to get a total effect over time
# as the system collapses back to steady-state...
# Create a matrix of the effects, which contain the total effects due to a
# perturbation over time.
# Bonus: correlate with the First, Second, Third, and sum of Matrix Powers?
# (High correlation means that the matrix powers do actually measure the effects
# of perturbations to a population from the steady-state.)
matsPerturbation <- list()
matsEffects1 <- list()
matsEffects2 <- list()
matsEffects3 <- list()
matsEffectsAdd <- list()
for (i in 1:nrow(communitiesAll)) {
communityThe <- communitiesAll[i, ]
if (!communityThe$Persists) {next}
matsPerturbation[[i]] <- matrix(NA,
nrow = communityThe$CommunitySize,
ncol = communityThe$CommunitySize)
# Each entry is the effect of the column on the row.
# Hence, we will be placing column vectors in the matrix.
#TODO Note to future me: double check the transpose, just in case.
for (r in 1:communityThe$CommunitySize) {
matsPerturbation[[i]][, r] <- with(
communityThe,
{
tempCommunity <- RMTRCode2::CsvRowSplit(Communities)
perturbation <- rep(0, CommunitySize)
abund <- RMTRCode2::CsvRowSplit(CommunityAbund)
perturbation[r] <- 1#0.0001 * abund[r]
dynamics <- RMTRCode2::LawMorton1996_NumIntegration(
A = mats[[DatasetID]][[CombnNum]][tempCommunity, tempCommunity],
R = pools[[DatasetID]][[CombnNum]]$ReproductionRate[tempCommunity],
X = abund + perturbation,
OuterTimeStepSize = 1,
InnerTimeStepSize = 0.001,
Tolerance = 1E-6
) # Column: Species, Row: Time
timediff <- diff(dynamics[, 1])
dynamics <- dynamics[, -1]
unlist(lapply(1:ncol(dynamics), FUN = function(nc, x, x0, t) {
sum((x[-1, nc] - x0[nc]) * timediff)
}, x = dynamics, x0 = abund, t = timediff))
}
)
}
# Compute matsEffects^n
matsEffects1[[i]] <- with(
communityThe,
{
tempCommunity <- RMTRCode2::CsvRowSplit(Communities)
abund <- RMTRCode2::CsvRowSplit(CommunityAbund)
tempmat <- mats[[DatasetID]][[CombnNum]][tempCommunity, tempCommunity]
do.call(cbind, lapply(1:ncol(tempmat), FUN = function(nc, x, x0) {
(x[, nc] * x0[nc])
}, x = tempmat, x0 = abund))
}
)
matsEffects2[[i]] <- expm::`%^%`(matsEffects1[[i]], 2)
matsEffects3[[i]] <- expm::`%^%`(matsEffects1[[i]], 3)
matsEffectsAdd[[i]] <-
matsEffects1[[i]] + matsEffects2[[i]] + matsEffects3[[i]]
}
# Average correlation between matrix entries across all matrices
print("Perturbation vs 1st Power:")
[1] "Perturbation vs 1st Power:"
mean(unlist(lapply(seq_along(matsPerturbation), function(i, m1, m2) {
if (is.null(m1[[i]])) return(NULL)
cor(m1[[i]][1:(nrow(m1[[i]])^2)],
m2[[i]][1:(nrow(m2[[i]])^2)])
}, m1 = matsPerturbation, m2 = matsEffects1)))
[1] 0.296493
print("Perturbation vs 2nd Power:")
[1] "Perturbation vs 2nd Power:"
mean(unlist(lapply(seq_along(matsPerturbation), function(i, m1, m2) {
if (is.null(m1[[i]])) return(NULL)
cor(m1[[i]][1:(nrow(m1[[i]])^2)],
m2[[i]][1:(nrow(m2[[i]])^2)])
}, m1 = matsPerturbation, m2 = matsEffects2)))
[1] -0.2894795
print("Perturbation vs 3rd Power:")
[1] "Perturbation vs 3rd Power:"
mean(unlist(lapply(seq_along(matsPerturbation), function(i, m1, m2) {
if (is.null(m1[[i]])) return(NULL)
cor(m1[[i]][1:(nrow(m1[[i]])^2)],
m2[[i]][1:(nrow(m2[[i]])^2)])
}, m1 = matsPerturbation, m2 = matsEffects3)))
[1] -0.5167285
print("Perturbation vs Sum of 1:3 powers:")
[1] "Perturbation vs Sum of 1:3 powers:"
mean(unlist(lapply(seq_along(matsPerturbation), function(i, m1, m2) {
if (is.null(m1[[i]])) return(NULL)
cor(m1[[i]][1:(nrow(m1[[i]])^2)],
m2[[i]][1:(nrow(m2[[i]])^2)])
}, m1 = matsPerturbation, m2 = matsEffectsAdd)))
[1] 0.2579495
This turns out to be fairly sensitive to the timescale considered (1 time unit versus 100 for instance), but not obviously so for the (absolute rather than relative) perturbation size (1 vs 0.1 or 0.01). Changing from absolute to relative greatly reduces the correlation to values between -0.2 and -0.05 roughly for values of 0.01, 0.001, and 0.0001.
As for how we can use the matrix, one easy set of summary statistics is to look for the proportions of various relationship types.
matsPerturbationsProps <- do.call(rbind, lapply(matsPerturbation, function(m) {
if (is.null(m)) return(
data.frame(
SelfRegulationPos = NA,
SelfRegulationNeg = NA,
Mutualism = NA,
Competition = NA,
Exploitation = NA
)
)
mutual <- 0
compet <- 0
exploi <- 0
inters <- 0
for (i in 1:(nrow(m) - 1)) {
for (j in (i+1):(ncol(m))) {
if (m[i, j] > 0 && m[j, i] > 0) mutual <- mutual + 1
else if (m[i, j] < 0 && m[j, i] < 0) compet <- compet + 1
else exploi <- exploi + 1
inters <- inters + 1 # expecting (nrow(m) * (nrow(m) - 1) / 2)
}
}
data.frame(
SelfRegulationPos = sum(diag(m) > 0) / nrow(m),
SelfRegulationNeg = sum(diag(m) < 0) / nrow(m),
Mutualism = mutual / inters,
Competition = compet / inters,
Exploitation = exploi / inters
)
}))
cbind(communitiesAll, matsPerturbationsProps)
LS0tDQp0aXRsZTogIkFuc3dlcmluZyBRdWVzdGlvbnM7IEdhdGhlciBEYXRhLCAyMDIxLTA1Ig0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KLS0tDQoNCmBgYHtyIGxpYnMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIENoZWNrIHJlcXVpc2l0ZSBwYWNrYWdlcyBhcmUgaW5zdGFsbGVkLg0KcGFja2FnZXMgPC0gYygNCiAgInBsb3RseSIsIA0KICAiZHBseXIiLA0KICAiY2hlZGRhciIsDQogICJpZ3JhcGgiLA0KICAiZXhwbSIsDQogICJSTVRSQ29kZTIiDQopDQpmb3IgKHBrZyBpbiBwYWNrYWdlcykgew0KICBsaWJyYXJ5KHBrZywgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKQ0KfQ0KDQojIFJlc2VydmVkIE5hbWVzDQpjYW5kaWRhdGVEYXRhIDwtIE5VTEwNCmlzbGFuZEludGVyYWN0aW9uc09uZUVtcHR5VHdvV2hpY2ggPC0gTlVMTA0KaXNsYW5kSW50ZXJhY3Rpb25zT25lVHdvIDwtIE5VTEwNCmlzbGFuZEludGVyYWN0aW9uc09uZVR3b1doaWNoIDwtIE5VTEwNCm1hdHMgPC0gTlVMTA0KcGFyYW1GcmFtZSA8LSBOVUxMDQpwbG90U2NhbGluZ0RhdGEgPC0gTlVMTA0KcG9vbHMgPC0gTlVMTA0KYGBgDQoNCiMgRGlzZW50YW5nbGluZyBFZmZlY3RzIG9uIHRoZSBWaWtpbmcgRGF0YSB7LnRhYnNldH0NCg0KIyMgTG9hZCBEYXRhDQpgYGB7ciBsb2FkRGF0fQ0KZWxsaXBzaXNBcHBseSA8LSBmdW5jdGlvbiguLi4sIEZVTikgew0KICBsYXBwbHkoYXMubGlzdCguLi4pLCBGVU4pDQp9DQoNCmxvYWQoIkxNMTk5Ni1OdW1Qb29sQ29tLVFEYXQtMjAyMS0wNS5SRGF0YSIpDQojIFN0b3AgaWYgbm90IGFsbCBhcmUgbm90IG51bGwNCnN0b3BpZm5vdChhbGwodW5saXN0KGVsbGlwc2lzQXBwbHkoDQogIEZVTiA9IGZ1bmN0aW9uKGJvb2wpIHshaXMubnVsbChib29sKX0sDQogIGNhbmRpZGF0ZURhdGEsIA0KICBpc2xhbmRJbnRlcmFjdGlvbnNPbmVFbXB0eVR3bywNCiAgaXNsYW5kSW50ZXJhY3Rpb25zT25lRW1wdHlUd29XaGljaCwNCiAgaXNsYW5kSW50ZXJhY3Rpb25zT25lVHdvLA0KICBpc2xhbmRJbnRlcmFjdGlvbnNPbmVUd29XaGljaCwNCiAgbWF0cywNCiAgcGFyYW1GcmFtZSwNCiAgcGxvdFNjYWxpbmdEYXRhLA0KICBwb29scw0KKSkpKQ0KYGBgDQoNCmBgYHtyIHRlc3RQbG90fQ0KcGxvdFNjYWxpbmcgPC0gcGxvdGx5OjpwbG90X2x5KA0KICBwbG90U2NhbGluZ0RhdGEsDQogIHggPSB+QmFzYWxzLA0KICB5ID0gfkNvbnN1bWVycywNCiAgeiA9IH5Db21tdW5pdHlTaXplLA0KICBjb2xvciA9IH5EYXRhc2V0LA0KICBjb2xvcnMgPSBjKCJyZWQiLCAiYmx1ZSIsICJibGFjayIpDQopDQoNCnBsb3RTY2FsaW5nIDwtIHBsb3RseTo6YWRkX21hcmtlcnMocGxvdFNjYWxpbmcpDQoNCnBsb3RTY2FsaW5nIDwtIHBsb3RseTo6bGF5b3V0KA0KICBwbG90U2NhbGluZywNCiAgc2NlbmUgPSBsaXN0KA0KICAgIHhheGlzID0gbGlzdCh0eXBlID0gImxvZyIpLA0KICAgIHlheGlzID0gbGlzdCh0eXBlID0gImxvZyIpLA0KICAgIGNhbWVyYSA9IGxpc3QoDQogICAgICBleWUgPSBsaXN0KA0KICAgICAgICB4ID0gLTEuMjUsIHkgPSAtMS4yNSwgeiA9IC4wNQ0KICAgICAgKQ0KICAgICkNCiAgKQ0KKQ0KDQpwbG90U2NhbGluZw0KYGBgDQoNCg0KYGBge3IgY29tbXVuaXRpZXNBbGxTYW5pdHlDaGVja3N9DQojIENoZWNrIHRoYXQgdGhlIFR3byBpc2xhbmQgYW5kIFRocmVlIGlzbGFuZCBzY2VuYXJpb3MgYXJlIHNldC11cCB0aGUgc2FtZS4NCnN0b3BpZm5vdCh1bmxpc3QobGFwcGx5KGlzbGFuZEludGVyYWN0aW9uc09uZVR3bywgbGVuZ3RoKSkgPT0gDQogICAgICAgICAgICB1bmxpc3QobGFwcGx5KGlzbGFuZEludGVyYWN0aW9uc09uZUVtcHR5VHdvLCBsZW5ndGgpKSkNCnN0b3BpZm5vdChuYW1lcyhpc2xhbmRJbnRlcmFjdGlvbnNPbmVUd28pID09IA0KICAgICAgICAgICAgbmFtZXMoaXNsYW5kSW50ZXJhY3Rpb25zT25lRW1wdHlUd28pKQ0KIyBDaGVjayB0aGF0IHRoZSBXaGljaCB2ZXJzaW9ucyBjb3JyZXNwb25kIGNvcnJlY3RseS4NCnN0b3BpZm5vdCgNCiAgdW5saXN0KGxhcHBseShpc2xhbmRJbnRlcmFjdGlvbnNPbmVUd29XaGljaCwgZnVuY3Rpb24oeCkgew0KICAgIGxlbmd0aChSTVRSQ29kZTI6OkNzdlJvd1NwbGl0KHgpKQ0KICB9KSkgDQogID09IHVubGlzdChsYXBwbHkoaXNsYW5kSW50ZXJhY3Rpb25zT25lVHdvLCBmdW5jdGlvbih4KSB7DQogICAgIyBXZSdyZSBsaWtlIG9uaW9uczsgd2UgaGF2ZSBMQVlFUlMhDQogICAgbGFwcGx5KHgsIGZ1bmN0aW9uKHkpIHsNCiAgICAgIGxhcHBseSh5LCBmdW5jdGlvbih6KSB7DQogICAgICAgIHN1bSh6ID4gMUUtNikgIyBIb3cgbWFueSAibGFyZ2UiIGVudHJpZXMgYXJlIHRoZXJlPw0KICAgICAgfSl9KX0pKQ0KKQ0Kc3RvcGlmbm90KA0KICB1bmxpc3QobGFwcGx5KGlzbGFuZEludGVyYWN0aW9uc09uZUVtcHR5VHdvV2hpY2gsIGZ1bmN0aW9uKHgpIHsNCiAgICBsZW5ndGgoUk1UUkNvZGUyOjpDc3ZSb3dTcGxpdCh4KSkNCiAgfSkpIA0KICA9PSB1bmxpc3QobGFwcGx5KGlzbGFuZEludGVyYWN0aW9uc09uZUVtcHR5VHdvLCBmdW5jdGlvbih4KSB7DQogICAgIyBXZSdyZSBsaWtlIG9uaW9uczsgd2UgaGF2ZSBMQVlFUlMhDQogICAgbGFwcGx5KHgsIGZ1bmN0aW9uKHkpIHsNCiAgICAgIGxhcHBseSh5LCBmdW5jdGlvbih6KSB7DQogICAgICAgIHN1bSh6ID4gMUUtNikgIyBIb3cgbWFueSAibGFyZ2UiIGVudHJpZXMgYXJlIHRoZXJlPw0KICAgICAgfSl9KX0pKQ0KKQ0KYGBgDQoNCmBgYHtyIGNvbW11bml0aWVzQWxsQWRkSHlicmlkc30NCiMgSHlicmlkcw0KIyBDcmVhdGUgYSBjb3VudCBvZiBob3cgbWFueSB0aW1lcyBlYWNoIGVudHJ5IHdpbGwgYmUgcmVwZWF0ZWQuDQpjb21tdW5pdGllc0FsbFJlcGVhdGVyIDwtIDUgKiB1bmxpc3QobGFwcGx5KGlzbGFuZEludGVyYWN0aW9uc09uZVR3bywgbGVuZ3RoKSkNCg0KIyBDcmVhdGUgdGVtcGxhdGUuDQpjb21tdW5pdGllc0FsbCA8LSBkYXRhLmZyYW1lKA0KICBDb21ibk51bSA9IHJlcCgwLCBzdW0oY29tbXVuaXRpZXNBbGxSZXBlYXRlcikpLCAjIFNob3VsZCByZXBlYXQgYWxsIHJvd3MuDQogIEJhc2FscyA9IDAsDQogIENvbnN1bWVycyA9IDAsDQogIERhdGFzZXQgPSAiIiwNCiAgRGF0YXNldElEID0gMCwNCiAgQ29tbXVuaXRpZXMgPSAiIiwNCiAgQ29tbXVuaXR5U2l6ZSA9IDAsDQogIE90aGVyU3RlYWR5U3RhdGVzID0gMCwgIyBUbyBiZSByZWNhbGN1bGF0ZWQNCiAgQ29tbXVuaXR5QWJ1bmQgPSAiIiwNCiAgQ29tbXVuaXR5UHJvZCA9IDAsDQogIFRvdGFsSUQgPSAiIiwNCiAgIyBBZGRpdGlvbmFsIENvbHVtbiEsIDEgZm9yIGRpcmVjdCBhc3NlbWJseSwgMCB1bnVzZWQuDQogIElzbGFuZHNVc2VkID0gcmVwKGMoMiwyLDMsMywzKSwgc3VtKGNvbW11bml0aWVzQWxsUmVwZWF0ZXIpLzUpDQopDQoNCiMgUmV0cmlldmUgdGhlIHJvd3MgdXNlZCB0byBtYWtlIGh5YnJpZHMNCmNvbW11bml0aWVzQWxsUHJvc3BlY3RzIDwtIGNhbmRpZGF0ZURhdGEgJT4lIGRwbHlyOjpncm91cF9ieSgNCiAgQ29tYm5OdW0sIEJhc2FscywgQ29uc3VtZXJzLCBEYXRhc2V0LCBEYXRhc2V0SUQsIFRvdGFsSUQNCikgJT4lIGRwbHlyOjpzZWxlY3QoDQogIENvbWJuTnVtOkRhdGFzZXRJRCwgVG90YWxJRA0KKSAlPiUgZHBseXI6OnN1bW1hcmlzZSgNCiAgQ291bnQgPSBkcGx5cjo6bigpLCAuZ3JvdXBzID0gImtlZXAiDQopICU+JSBkcGx5cjo6ZmlsdGVyKA0KICBDb3VudCA+IDENCikgJT4lIGRwbHlyOjpzZWxlY3QoDQogIC1Db3VudA0KKSAlPiUgZHBseXI6OmFycmFuZ2UoDQogIERhdGFzZXRJRCwgQ29tYm5OdW0NCikNCg0KIyBNYWtlIHN1cmUgdGhhdCB0aGUgbmFtZXMgbWF0Y2guDQpzdG9waWZub3QoY29tbXVuaXRpZXNBbGxQcm9zcGVjdHMkVG90YWxJRCA9PSBuYW1lcyhjb21tdW5pdGllc0FsbFJlcGVhdGVyKSkNCg0KIyBJbnNlcnQgcmVwZXRpdGlvbnMuDQpjb21tdW5pdGllc0FsbCRDb21ibk51bSA8LSByZXAoY29tbXVuaXRpZXNBbGxQcm9zcGVjdHMkQ29tYm5OdW0sIGNvbW11bml0aWVzQWxsUmVwZWF0ZXIpDQpjb21tdW5pdGllc0FsbCRCYXNhbHMgPC0gcmVwKGNvbW11bml0aWVzQWxsUHJvc3BlY3RzJEJhc2FscywgY29tbXVuaXRpZXNBbGxSZXBlYXRlcikNCmNvbW11bml0aWVzQWxsJENvbnN1bWVycyA8LSByZXAoY29tbXVuaXRpZXNBbGxQcm9zcGVjdHMkQ29uc3VtZXJzLCBjb21tdW5pdGllc0FsbFJlcGVhdGVyKQ0KY29tbXVuaXRpZXNBbGwkRGF0YXNldCA8LSByZXAoY29tbXVuaXRpZXNBbGxQcm9zcGVjdHMkRGF0YXNldCwgY29tbXVuaXRpZXNBbGxSZXBlYXRlcikNCmNvbW11bml0aWVzQWxsJERhdGFzZXRJRCA8LSByZXAoY29tbXVuaXRpZXNBbGxQcm9zcGVjdHMkRGF0YXNldElELCBjb21tdW5pdGllc0FsbFJlcGVhdGVyKQ0KY29tbXVuaXRpZXNBbGwkVG90YWxJRCA8LSByZXAoY29tbXVuaXRpZXNBbGxQcm9zcGVjdHMkVG90YWxJRCwgY29tbXVuaXRpZXNBbGxSZXBlYXRlcikNCg0KIyBUbyBtb3ZlIG92ZXIgZnJvbSB0aGUgZGF0YS4NCiMgQ29tbXVuaXRpZXMgPSAiIiwNCiMgQ29tbXVuaXR5QWJ1bmQgPSAiIg0KDQpjb21tdW5pdGllc0FsbFtjb21tdW5pdGllc0FsbCRJc2xhbmRzVXNlZCA9PSAyLCBdJENvbW11bml0aWVzIDwtIA0KICBpc2xhbmRJbnRlcmFjdGlvbnNPbmVUd29XaGljaA0KY29tbXVuaXRpZXNBbGxbY29tbXVuaXRpZXNBbGwkSXNsYW5kc1VzZWQgPT0gMywgXSRDb21tdW5pdGllcyA8LSANCiAgaXNsYW5kSW50ZXJhY3Rpb25zT25lRW1wdHlUd29XaGljaA0KDQpjb21tdW5pdGllc0FsbFtjb21tdW5pdGllc0FsbCRJc2xhbmRzVXNlZCA9PSAyLCBdJENvbW11bml0eUFidW5kIDwtIA0KICAjIFdlJ3JlIGxpa2Ugb25pb25zOyB3ZSBoYXZlIExBWUVSUyENCiAgdW5saXN0KGxhcHBseShpc2xhbmRJbnRlcmFjdGlvbnNPbmVUd28sIGZ1bmN0aW9uKHgpIHsNCiAgICBsYXBwbHkoeCwgZnVuY3Rpb24oeSkgew0KICAgICAgbGFwcGx5KHksIGZ1bmN0aW9uKHopIHsNCiAgICAgICAgdG9TdHJpbmcoelt6ID4gMUUtNl0pDQogICAgICB9KQ0KICAgIH0pDQogIH0pKQ0KICANCmNvbW11bml0aWVzQWxsW2NvbW11bml0aWVzQWxsJElzbGFuZHNVc2VkID09IDMsIF0kQ29tbXVuaXR5QWJ1bmQgPC0gDQogIHVubGlzdChsYXBwbHkoaXNsYW5kSW50ZXJhY3Rpb25zT25lRW1wdHlUd28sIGZ1bmN0aW9uKHgpIHsNCiAgICBsYXBwbHkoeCwgZnVuY3Rpb24oeSkgew0KICAgICAgbGFwcGx5KHksIGZ1bmN0aW9uKHopIHsNCiAgICAgICAgdG9TdHJpbmcoelt6ID4gMUUtNl0pDQogICAgICB9KQ0KICAgIH0pDQogIH0pKQ0KDQojIFRvIGNhbGN1bGF0ZSBmcm9tIHRoZSBkYXRhLg0KIyBDb21tdW5pdHlTaXplID0gMCwgIyBUbyBiZSBjYWxjdWxhdGVkIGZyb20gQ29tbXVuaXRpZXMuDQojIE90aGVyU3RlYWR5U3RhdGVzID0gMCwgIyBUbyBiZSByZWNhbGN1bGF0ZWQgbGFzdCBhZnRlciBmaWx0ZXJpbmcNCiMgQ29tbXVuaXR5UHJvZCA9IDAsICMgVG8gYmUgcmVjYWxjdWxhdGVkIGFmdGVyIEFidW5kIHN0b3JlZC4NCmNvbW11bml0aWVzQWxsJENvbW11bml0eVNpemUgPC0gdW5saXN0KGxhcHBseSgNCiAgY29tbXVuaXRpZXNBbGwkQ29tbXVuaXRpZXMsIGZ1bmN0aW9uKHgpIHsNCiAgICBsZW5ndGgoUk1UUkNvZGUyOjpDc3ZSb3dTcGxpdCh4KSkNCiAgfSkpIA0KDQpmb3IgKHIgaW4gMTpucm93KGNvbW11bml0aWVzQWxsKSkgew0KICBjb21tdW5pdGllc0FsbCRDb21tdW5pdHlQcm9kW3JdIDwtIHdpdGgoDQogICAgY29tbXVuaXRpZXNBbGxbciwgXSwgDQogICAgUk1UUkNvZGUyOjpQcm9kdWN0aXZpdHkoDQogICAgICBQb29sID0gcG9vbHNbW0RhdGFzZXRJRF1dW1tDb21ibk51bV1dLCANCiAgICAgIEludGVyYWN0aW9uTWF0cml4ID0gbWF0c1tbRGF0YXNldElEXV1bW0NvbWJuTnVtXV0sIA0KICAgICAgQ29tbXVuaXR5ID0gQ29tbXVuaXRpZXMsIA0KICAgICAgUG9wdWxhdGlvbnMgPSBDb21tdW5pdHlBYnVuZA0KICAgICkNCiAgKQ0KfQ0KYGBgDQoNCmBgYHtyIGNvbW11bml0aWVzQWxsQWRkT3JpZ2luYWxzfQ0KIyBPcmlnaW5hbCBzeXN0ZW1zDQpjb21tdW5pdGllc0FsbCA8LSByYmluZCgNCiAgY2FuZGlkYXRlRGF0YSAlPiUgZHBseXI6OnNlbGVjdCgNCiAgICAtQ29tbXVuaXR5RnJlcSwgLUNvbW11bml0eVNlcQ0KICApICU+JSBkcGx5cjo6bXV0YXRlKA0KICAgIElzbGFuZHNVc2VkID0gMQ0KICApLCANCiAgY29tbXVuaXRpZXNBbGwNCikNCg0KYGBgDQoNCmBgYHtyIGNvbW11bml0aWVzQWxsSGFzaH0NCiMgVHJlYXRpbmcgdGhlIFByb2R1Y3Rpdml0eSBsaWtlIG9uZSBtaWdodCB0cmVhdCBhIGhhc2gsDQojIGlmIHR3byByb3dzIHdpdGggdGhlIHNhbWUgcHJvcGVydGllcyBhcmUgYXNzaWduZWQgdGhlIHNhbWUgaGFzaCwgDQojIHdlIG9ubHkga2VlcCBvbmUuIA0KIyBPbmUgZGVjaW1hbCBwbGFjZSBtaWdodCBiZSBleGNlc3NpdmUsIA0KIyBidXQgd2UgY2FuIHJlZmxlY3Qgb24gdGhhdCBpZiByZXN1bHRzIGRvd24gdGhlIGxpbmUgYXJlIG5vdCBpbnRlcmVzdGluZy4NCiMgRm9yIHRoZSByZWNvcmQgdGhvdWdoLCBpdCBhcHBlYXJzIHRoYXQgaXQgaXMgYSBkZWNlbnRseSBnb29kIGFwcHJvYWNoIGF0IA0KIyByZW1vdmluZyBlZmZlY3RpdmVseSBudW1lcmljYWwgZHVwbGljYXRlcy4NCiMgTm90IGJvdGhlcmluZywgc29ydCBvZiwgd2l0aCBJc2xhbmRzVXNlZCwgc2luY2UgbWFueSB0aW1lcyBhIGNvbW11bml0eSBpcw0KIyByZXByb2R1Y2VkIG9uIHZhcnlpbmcgbnVtYmVycyBvZiBpc2xhbmRzLg0KDQojIGNvbW11bml0aWVzQWxsIDwtIGNvbW11bml0aWVzQWxsICU+JSBkcGx5cjo6bXV0YXRlKA0KIyAgIHRlbXBQcm9kID0gcm91bmQoQ29tbXVuaXR5UHJvZCwgMikNCiMgKSAlPiUgZHBseXI6OmRpc3RpbmN0KA0KIyAgIENvbWJuTnVtLCBCYXNhbHMsIENvbnN1bWVycywgRGF0YXNldCwgRGF0YXNldElELCBUb3RhbElELA0KIyAgIENvbW11bml0aWVzLCBDb21tdW5pdHlTaXplLCB0ZW1wUHJvZCwgSXNsYW5kc1VzZWQsDQojICAgLmtlZXBfYWxsID0gVFJVRQ0KIyApICU+JSBkcGx5cjo6c2VsZWN0KA0KIyAgIC10ZW1wUHJvZA0KIyApDQoNCmNvbW11bml0aWVzQWxsIDwtIGNvbW11bml0aWVzQWxsICU+JSBkcGx5cjo6bXV0YXRlKA0KICB0ZW1wUHJvZCA9IHJvdW5kKENvbW11bml0eVByb2QsIDIpDQopICU+JSBkcGx5cjo6Z3JvdXBfYnkoDQogIENvbWJuTnVtLCBCYXNhbHMsIENvbnN1bWVycywgRGF0YXNldCwgRGF0YXNldElELCBUb3RhbElELA0KICBDb21tdW5pdGllcywgQ29tbXVuaXR5U2l6ZSwgdGVtcFByb2QsDQopICU+JSBkcGx5cjo6c3VtbWFyaXNlKA0KICBDb21tdW5pdHlBYnVuZCA9IGRwbHlyOjpmaXJzdChDb21tdW5pdHlBYnVuZCksDQogIENvbW11bml0eVByb2QgPSBkcGx5cjo6Zmlyc3QoQ29tbXVuaXR5UHJvZCksDQogIElzbGFuZHNVc2VkID0gdG9TdHJpbmcodW5pcXVlKElzbGFuZHNVc2VkKSksDQogIC5ncm91cHMgPSAiZHJvcCINCikgJT4lIGRwbHlyOjpzZWxlY3QoDQogIC10ZW1wUHJvZA0KKSAlPiUgZHBseXI6Omdyb3VwX2J5KA0KICBDb21ibk51bSwgQmFzYWxzLCBDb25zdW1lcnMsIERhdGFzZXQsIERhdGFzZXRJRCwgVG90YWxJRA0KKSAlPiUgZHBseXI6Om11dGF0ZSgNCiAgT3RoZXJTdGVhZHlTdGF0ZXMgPSBkcGx5cjo6bigpIC0gMSwNCiAgSXNsYW5kczEgPSBncmVwbChwYXR0ZXJuID0gIjEiLCBJc2xhbmRzVXNlZCwgZml4ZWQgPSBUUlVFKSAjIFdpbGwgYmUgdXNlZnVsDQopDQoNCmBgYA0KDQojIyBQZXJzaXN0ZW5jZSBvZiBIeWJyaWQgQ29tbXVuaXRpZXMNClRoZSBpZGVhIGlzIHN0cmFpZ2h0Zm9yd2FyZDogYWZ0ZXIgYWxsb3dpbmcgaW50ZXJhY3Rpb25zIGJldHdlZW4gaXNsYW5kcywgZm9yIGlzbGFuZHMgdGhhdCBhcmUgbm90IHRoZSBzYW1lIGFzIG9uZSBvZiB0aGUgb3JpZ2luYWwgY29tbXVuaXRpZXMsIGlzb2xhdGUgdGhlIGlzbGFuZCBhbmQgY2hlY2sgdG8gc2VlIHdoYXQgaGFwcGVucy4NCg0KYGBge3IgaHlicmlkc09ubHl9DQpjb21tdW5pdGllc0h5YnJpZHMgPC0gY29tbXVuaXRpZXNBbGwgJT4lIGRwbHlyOjpmaWx0ZXIoDQogICFJc2xhbmRzMQ0KKSAlPiUgZHBseXI6OnNlbGVjdCgtSXNsYW5kczEpDQpgYGANCg0KYGBge3IgYXBwbHlEeW5hbWljc30NCmNvbW11bml0aWVzSHlicmlkcyRBZnRlclNlcEFidW5kIDwtICIiDQpjb21tdW5pdGllc0h5YnJpZHMkQWZ0ZXJTZXBDb21tdW5pdHkgPC0gIiINCmNvbW11bml0aWVzSHlicmlkcyRBZnRlclNlcENvbW11bml0eVNpemUgPC0gMA0KY29tbXVuaXRpZXNIeWJyaWRzJEFmdGVyU2VwUHJvZHVjdGlvbiA8LSAwDQpmb3IgKHIgaW4gMTpucm93KGNvbW11bml0aWVzSHlicmlkcykpIHsNCiAgdGVtcCA8LSB3aXRoKA0KICAgIGNvbW11bml0aWVzSHlicmlkc1tyLCBdLA0KICAgIHsgICAgDQogICAgICB0ZW1wIDwtIFJNVFJDb2RlMjo6Q3N2Um93U3BsaXQoQ29tbXVuaXRpZXMpDQogICAgICBSTVRSQ29kZTI6Okxhd01vcnRvbjE5OTZfTnVtSW50ZWdyYXRpb24oDQogICAgICAgIEEgPSBtYXRzW1tEYXRhc2V0SURdXVtbQ29tYm5OdW1dXVt0ZW1wLCB0ZW1wXSwNCiAgICAgICAgUiA9IHBvb2xzW1tEYXRhc2V0SURdXVtbQ29tYm5OdW1dXSRSZXByb2R1Y3Rpb25SYXRlW3RlbXBdLA0KICAgICAgICBYID0gUk1UUkNvZGUyOjpDc3ZSb3dTcGxpdChDb21tdW5pdHlBYnVuZCksIA0KICAgICAgICBPdXRlclRpbWVTdGVwU2l6ZSA9IDNFNCwNCiAgICAgICAgVG9sZXJhbmNlID0gMUUtNg0KICAgICAgKSAjIHJldHJpZXZlIHRoZSBhYnVuZGFuY2Ugb3ZlciB0aW1lIG1hdHJpeA0KICAgIH0NCiAgKSANCiAgDQogIHRlbXAgPC0gdGVtcFtucm93KHRlbXApLCAtMV0gIyBjaG9vc2UgbGFzdCByb3csIHJlbW92ZSB0aW1lIGNvbHVtbi4NCiAgDQogIGNvbW11bml0aWVzSHlicmlkcyRBZnRlclNlcENvbW11bml0eVtyXSA8LSB0b1N0cmluZygNCiAgICBSTVRSQ29kZTI6OkNzdlJvd1NwbGl0KGNvbW11bml0aWVzSHlicmlkcyRDb21tdW5pdGllc1tyXSlbd2hpY2godGVtcCA+IDFFLTYpXQ0KICApDQogIA0KICB0ZW1wIDwtIHRlbXBbd2hpY2godGVtcCA+IDFFLTYpXSAjIHJlbW92ZSBtaWNyb2ZveGVzLg0KICANCiAgY29tbXVuaXRpZXNIeWJyaWRzJEFmdGVyU2VwQWJ1bmRbcl0gPC0gdG9TdHJpbmcodGVtcCkNCiAgY29tbXVuaXRpZXNIeWJyaWRzJEFmdGVyU2VwQ29tbXVuaXR5U2l6ZVtyXSA8LSBsZW5ndGgodGVtcCkNCiAgDQogIGNvbW11bml0aWVzSHlicmlkcyRBZnRlclNlcFByb2R1Y3Rpb25bcl0gPC0gd2l0aCgNCiAgICBjb21tdW5pdGllc0h5YnJpZHNbciwgXSwgDQogICAgUk1UUkNvZGUyOjpQcm9kdWN0aXZpdHkoDQogICAgICBQb29sID0gcG9vbHNbW0RhdGFzZXRJRF1dW1tDb21ibk51bV1dLCANCiAgICAgIEludGVyYWN0aW9uTWF0cml4ID0gbWF0c1tbRGF0YXNldElEXV1bW0NvbWJuTnVtXV0sIA0KICAgICAgQ29tbXVuaXR5ID0gQWZ0ZXJTZXBDb21tdW5pdHksIA0KICAgICAgUG9wdWxhdGlvbnMgPSBBZnRlclNlcEFidW5kDQogICAgKQ0KICApDQp9DQpgYGANCg0KYGBge3IgaHlicmlkc1BlcnNpc3R9DQpjb21tdW5pdGllc0h5YnJpZHMgPC0gY29tbXVuaXRpZXNIeWJyaWRzICU+JSBkcGx5cjo6bXV0YXRlKA0KICBQZXJzaXN0cyA9IEFmdGVyU2VwQ29tbXVuaXR5ID09IENvbW11bml0aWVzLA0KICBQcm9kQ2hhbmdlID0gQWZ0ZXJTZXBQcm9kdWN0aW9uIC0gQ29tbXVuaXR5UHJvZA0KKQ0KYGBgDQoNClNvIGFmdGVyIHJ1bm5pbmcgdGhlIGR5bmFtaWNzIGZvciAzRTQgdGltZSB1bml0cyAoaS5lLiAzeCB0aGUgbGVuZ3RoIG9mIHRpbWUgdGhlIGR5bmFtaWNzIGluIHRoZSBudW1lcmljYWwgYXNzZW1ibHkgcnVucyBpbiBiZXR3ZWVuIGFzc2VtYmx5IHN0ZXBzIGFuZCAxLjV4IHRoZSBsZW5ndGggb2YgdGhlIGlzbGFuZCBkeW5hbWljcyksIHRoZSBjb21tdW5pdGllcyB0aGF0IHBlcnNpc3QgYXJlIGByIHdoaWNoKGNvbW11bml0aWVzSHlicmlkcyRQZXJzaXN0cylgLg0KRXhhbWluaW5nIHRoZSBjb21tdW5pdGllcyB0aGVtc2VsdmVzLCB0aGV5IGFyZSBhbGwgdGhlIHNhbWUgY29tbXVuaXR5LCBhbGJlaXQgd2l0aCBkaWZmZXJlbnQgc3RhcnRpbmcgcG9pbnRzLg0KYGBge3IgaHlicmlkc1BlcnNpc3RXaGljaH0NCmNvbW11bml0aWVzSHlicmlkc1tjb21tdW5pdGllc0h5YnJpZHMkUGVyc2lzdHMsIF0NCmBgYA0KDQpBbiBvYnZpb3VzIGZvbGxvdy11cCBxdWVzdGlvbjogaG93IG1hbnkgb2YgdGhlIGNvbW11bml0aWVzIHRoYXQgY29sbGFwc2UgZG8gc28gdG8gY29tbXVuaXRpZXMgdGhhdCB3ZSBoYXZlIG5vdCBhbHJlYWR5IHNlZW4/DQoNCmBgYHtyIGh5YnJpZHNDb2xsYXBzZVRvfQ0KY29tbXVuaXRpZXNIeWJyaWRzIDwtIGNvbW11bml0aWVzSHlicmlkcyAlPiUgZHBseXI6Om11dGF0ZSgNCiAgQWZ0ZXJTZXBDb21tdW5pdHlBbHJlYWR5UHJlc2VudCA9IEFmdGVyU2VwQ29tbXVuaXR5ICVpbiUgY29tbXVuaXRpZXNBbGwkQ29tbXVuaXRpZXMNCikNCnN1bSghY29tbXVuaXRpZXNIeWJyaWRzJEFmdGVyU2VwQ29tbXVuaXR5QWxyZWFkeVByZXNlbnQpDQpgYGANCg0KQ29uc29saWRhdGluZyBkb3duIHRvIHVuaXF1ZSBlbmRpbmcgc3RhdGVzIHdlIGhhdmUgdGhlIGZvbGxvd2luZy4NCg0KYGBge3IgaHlicmlkc0NvbGxhcHNlV2hpY2h9DQpjb21tdW5pdGllc0h5YnJpZHNbDQogICFjb21tdW5pdGllc0h5YnJpZHMkQWZ0ZXJTZXBDb21tdW5pdHlBbHJlYWR5UHJlc2VudCwgDQogIF0gJT4lIGRwbHlyOjpkaXN0aW5jdChDb21ibk51bSwgQWZ0ZXJTZXBDb21tdW5pdHksIC5rZWVwX2FsbCA9IFRSVUUpDQpgYGANCg0KV2Ugd2lsbCBhZGQgdGhlc2UgbmV3IHN0YXRlcyB0byBvdXIgY2F0YWxvZ3VlIG9mIGNvbW11bml0aWVzIGZyb20gdGhlIGV4cGVyaW1lbnRzLg0KV2UgYWxzbyB0YWtlIHRoZSBhYnVuZGFuY2UgYWZ0ZXIgc2VwYXJhdGlvbiBpZiB0aGUgY29tbXVuaXR5IHBlcnNpc3RzIHRvIGJldHRlciByZWZsZWN0IHN0ZWFkeS1zdGF0ZSBjb25kaXRpb25zLg0KDQpgYGB7ciBoeWJyaWRzVG9BbGx9DQpjb21tdW5pdGllc0FsbCA8LSByYmluZCgNCiAgY29tbXVuaXRpZXNBbGwgJT4lIGRwbHlyOjpmaWx0ZXIoDQogICAgSXNsYW5kczEgPT0gVFJVRQ0KICApICU+JSBkcGx5cjo6bXV0YXRlKA0KICAgIEh5YnJpZENvbGxhcHNlID0gRkFMU0UsIFBlcnNpc3RzID0gVFJVRQ0KICApLA0KICBjb21tdW5pdGllc0h5YnJpZHMgJT4lIGRwbHlyOjptdXRhdGUoDQogICAgQ29tbXVuaXR5QWJ1bmQgPSBpZmVsc2UoUGVyc2lzdHMsIEFmdGVyU2VwQWJ1bmQsIENvbW11bml0eUFidW5kKSwNCiAgICBJc2xhbmRzMSA9IEZBTFNFLCBIeWJyaWRDb2xsYXBzZSA9IEZBTFNFLA0KICApICU+JSBkcGx5cjo6c2VsZWN0KA0KICAgIC1BZnRlclNlcEFidW5kLCAtQWZ0ZXJTZXBDb21tdW5pdHksIC1BZnRlclNlcENvbW11bml0eVNpemUsIA0KICAgIC1BZnRlclNlcFByb2R1Y3Rpb24sIC1Qcm9kQ2hhbmdlLCAtQWZ0ZXJTZXBDb21tdW5pdHlBbHJlYWR5UHJlc2VudA0KICApICwNCiAgd2l0aCgNCiAgICBjb21tdW5pdGllc0h5YnJpZHNbDQogICAgICAhY29tbXVuaXRpZXNIeWJyaWRzJEFmdGVyU2VwQ29tbXVuaXR5QWxyZWFkeVByZXNlbnQsIA0KICAgIF0gJT4lIGRwbHlyOjpkaXN0aW5jdChDb21ibk51bSwgQWZ0ZXJTZXBDb21tdW5pdHksIC5rZWVwX2FsbCA9IFRSVUUpLA0KICAgIGRhdGEuZnJhbWUoDQogICAgICBDb21ibk51bSA9IENvbWJuTnVtLA0KICAgICAgQmFzYWxzID0gQmFzYWxzLA0KICAgICAgQ29uc3VtZXJzID0gQ29uc3VtZXJzLA0KICAgICAgRGF0YXNldCA9IERhdGFzZXQsDQogICAgICBEYXRhc2V0SUQgPSBEYXRhc2V0SUQsDQogICAgICBUb3RhbElEID0gVG90YWxJRCwNCiAgICAgIENvbW11bml0aWVzID0gQWZ0ZXJTZXBDb21tdW5pdHksDQogICAgICBDb21tdW5pdHlTaXplID0gQWZ0ZXJTZXBDb21tdW5pdHlTaXplLA0KICAgICAgQ29tbXVuaXR5QWJ1bmQgPSBBZnRlclNlcEFidW5kLA0KICAgICAgQ29tbXVuaXR5UHJvZCA9IEFmdGVyU2VwUHJvZHVjdGlvbiwNCiAgICAgIElzbGFuZHNVc2VkID0gSXNsYW5kc1VzZWQsDQogICAgICBPdGhlclN0ZWFkeVN0YXRlcyA9IDAsDQogICAgICBJc2xhbmRzMSA9IEZBTFNFLA0KICAgICAgSHlicmlkQ29sbGFwc2UgPSBUUlVFLA0KICAgICAgUGVyc2lzdHMgPSBUUlVFLA0KICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQogICAgKSkNCikNCmBgYA0KDQojIyBJbnZhZGFiaWxpdHkgb2YgSHlicmlkIENvbW11bml0aWVzDQpMb29raW5nIGF0IGEgbG9uZ2VyIHRpbWUgc2NhbGUsIHdoYXQgaGFwcGVucyBpZi93aGVuIGludmFzaW9ucyByZXN1bWU/IERvIHRoZSBoeWJyaWQgY29tbXVuaXRpZXMgdGhhdCBlbWVyZ2VkIHJldGFpbiB0aGUgdW5pbnZhZGFiaWxpdHkgb2YgdGhlIHBhcmVudCBjb21tdW5pdGllcz8NCg0KVGhpcyBxdWVzdGlvbiBzaG91bGQgYmUgc3RyYWlnaHRmb3J3YXJkIGFzIGl0IGlzIHRlc3RpbmcgYSBzdGVwIGZyb20gdGhlIGFzc2VtYmx5IHByb2Nlc3MuDQpgYGB7ciBhbGxDb21tdW5pdGllc0ludmFkYWJsZX0NCmNvbW11bml0aWVzQWxsJFVuaW52YWRhYmxlIDwtIE5BDQpmb3IgKHIgaW4gMTpucm93KGNvbW11bml0aWVzQWxsKSkgew0KICBjb21tdW5pdGllc0FsbCRVbmludmFkYWJsZVtyXSA8LSB3aXRoKA0KICAgIGNvbW11bml0aWVzQWxsW3IsIF0sDQogICAgew0KICAgICAgdGVtcFJvdyA8LSByZXAoTkEsIG5yb3cocG9vbHNbW0RhdGFzZXRJRF1dW1tDb21ibk51bV1dKSArIDEpDQogICAgICB0ZW1wUm93W1JNVFJDb2RlMjo6Q3N2Um93U3BsaXQoQ29tbXVuaXRpZXMpICsgMV0gPC0gDQogICAgICAgIFJNVFJDb2RlMjo6Q3N2Um93U3BsaXQoQ29tbXVuaXR5QWJ1bmQpDQogICAgICBSTVRSQ29kZTI6Okxhd01vcnRvbjE5OTZfQ2hlY2tVbmludmFkYWJsZSgNCiAgICAgICAgQWJ1bmRhbmNlUm93ID0gdGVtcFJvdywNCiAgICAgICAgUG9vbCA9IHBvb2xzW1tEYXRhc2V0SURdXVtbQ29tYm5OdW1dXSwNCiAgICAgICAgQ29tbXVuaXR5TWF0cml4ID0gbWF0c1tbRGF0YXNldElEXV1bW0NvbWJuTnVtXV0NCiAgICAgICkNCiAgICB9DQogICkNCn0NCmBgYA0KDQpXZSBjYW4gY29tcGFyZSB0aGlzIHByb3BlcnR5IGFnYWluc3Qgc29tZSBvZiB0aGUgb3RoZXIgcHJvcGVydGllcy4NCg0KVW5pbnZhZGFiaWxpdHkgdmVyc3VzIHdoZXRoZXIgYSBjb21tdW5pdHkgd2FzIGZvdW5kIHZpYSBhc3NlbWJseSAoIm9uIElzbGFuZCAxIik6DQpgYGB7ciB0YWJsZVVuaW52YWRhYmxlSXNsYW5kMX0NCndpdGgoY29tbXVuaXRpZXNBbGwsDQogICAgIHRhYmxlKFVuaW52YWRhYmxlLCBJc2xhbmRzMSkpDQpgYGANCk5ldmVyIGludmFkYWJsZSBhbmQgYXNzZW1ibGVkIChnb29kISksIGJ1dCBhYm91dCBoYWxmIG9mIHVuaW52YWRhYmxlcyBhcmUgZm91bmQgd2l0aG91dCBiZWluZyBhc3NlbWJsZWQuDQpXaGF0IGFib3V0IG9mIHRob3NlIHRoYXQgcGVyc2lzdD8NCmBgYHtyIHRhYmxlVW5pbnZhZGFibGVJc2xhbmQxUGVyc2lzdH0NCndpdGgoY29tbXVuaXRpZXNBbGwgJT4lIGRwbHlyOjpmaWx0ZXIoUGVyc2lzdHMpLA0KICAgICB0YWJsZShVbmludmFkYWJsZSwgSXNsYW5kczEpKQ0KYGBgDQpXaGljaCBvZiBjb3Vyc2UgZmlsbHMgaW4gc29tZSBvZiB0aGUgYmxhbmtzLg0KU28gbm9uZSBvZiB0aGUgY29tbXVuaXRpZXMgdGhhdCBwZXJzaXN0IGFyZSB1bmludmFkYWJsZSBpZiB0aGV5IHdlcmUgbm90IGFuIGVuZCBzdGF0ZSBvZiB0aGUgYXNzZW1ibHkgcHJvY2Vzcy4NCg0KIyMgUHJlc2VuY2Ugb2YgTWFzcyBFZmZlY3RzDQpXZSBjaGVjayB0byBzZWUgd2hhdCBoYXBwZW5zIHdoZW4gd2UgdHJlYXQgZWFjaCBjb21tdW5pdHkgYXMgYSBwb29sIGZvciB0aGUgb3RoZXIgYW5kIHBlcmZvcm0gYXNzZW1ibHkuIEFyZSB0aGUgcmVzdWx0cyB0aGUgc2FtZSBhcyB0aGUgZGlmZnVzaW9uIHN5c3RlbT8NCg0KRmlyc3QsIHVwZGF0ZSB0aGUgcGFpcmluZ3MuDQpgYGB7ciB1cGRhdGVPdGhlclN0ZWFkeVN0YXRlc30NCmNvbW11bml0aWVzQWxsIDwtIGNvbW11bml0aWVzQWxsICU+JSBkcGx5cjo6Z3JvdXBfYnkoDQogIENvbWJuTnVtLCBCYXNhbHMsIENvbnN1bWVycywgRGF0YXNldCwgRGF0YXNldElELCBUb3RhbElEDQopICU+JSBkcGx5cjo6bXV0YXRlKA0KICBPdGhlclN0ZWFkeVN0YXRlcyA9IGRwbHlyOjpuKCkgLSAxDQopICU+JSBkcGx5cjo6dW5ncm91cCgpDQpgYGANCg0KVGhpcyBwcm9jZWR1cmUgY2FuIGJlIGRvbmUgaW4gdHdvIHdheXM6IGZpcnN0IGJ5IGRpcmVjdGVkIGludmFzaW9uIHdoZXJlIG9uZSBjb21tdW5pdHkgaXMgYSBwb29sIGZvciB0aGUgb3RoZXIsIGFuZCBzZWNvbmQgd2l0aCBtdXR1YWwgKHVuZGlyZWN0ZWQpIGludmFzaW9uIHdoZXJlIGJvdGggY29tbXVuaXRpZXMgYXJlIHNpbXVsdGFuZW91c2x5IHBvb2xzIGZvciBhbmQgaW52YWRlZCBieSBlYWNoIG90aGVyLiANCk5vdGUgdGhhdCBpbiB0aGUgZGlyZWN0ZWQgY2FzZSwgd2UgZG8gbm90IG5lZWQgdG8gZG8gYW55IG9mIHRoZSBjb21tdW5pdGllcyBhbHJlYWR5IG1hcmtlZCBhcyB1bmludmFkYWJsZSB3aXRoIHJlc3BlY3QgdG8gdGhlICpyZWdpb25hbCogcG9vbHMuDQpUaGUgb3RoZXIgY29tbXVuaXRpZXMgdGhleSB3b3VsZCBiZSBjb21wYXJlZCB3aXRoIGFyZSBzdWJzZXRzIG9mIHRoZSByZWdpb25hbCBwb29scywgYW5kIHNvIHdvdWxkIGFscmVhZHkgYmUgY2hlY2tlZCBhZ2FpbnN0Lg0KV2UgdGh1cyBoYXZlIG1hdHJpY2VzIHdpdGggdGhyZWUgcG9zc2libGUgb3V0Y29tZXMgZm9yIGVudHJpZXM6IGEgc2V0IG9mIG5ldyBjb21tdW5pdGllcywgdW5pbnZhZGFiaWxpdHksIG9yIGBOQWAgZm9yIHVuZXZhbHVhdGVkIGVudHJpZXMuIA0KSW4gdGhlIGRpcmVjdGVkIGNhc2Ugd2UgdGFrZSBhIHJvdyBmb3Igb3VyIGludmFkZXIvcG9vbCBhbmQgY29sdW1uIGZvciB0aGUgaW52YWRlZCBjb21tdW5pdHksIHN1Y2ggdGhhdCBhIGNvbW11bml0eSBpcyB1bmludmFkYWJsZSB3aXRoIHJlc3BlY3QgdG8gYWxsIG90aGVyIGNvbW11bml0aWVzIGlmIGl0cyBjb2x1bW4gb25seSBjb250YWlucyBgRkFMU0VgLiANCihBIGNvbW11bml0eSBpcyB1bmludmFkYWJsZSBieSBpdHNlbGYgZm9yIHNha2Ugb2YgYXJndW1lbnQuKQ0KDQpgYGB7ciBpbnZhc2lvbkRpcmVjdGVkfQ0KaW52YXNpb25zRGlyZWN0ZWQgPC0gbGlzdCgpDQpmb3IgKGdycCBpbiB1bmlxdWUoY29tbXVuaXRpZXNBbGwkVG90YWxJRCkpIHsNCiAgY29tbXVuaXRpZXNHcnAgPC0gY29tbXVuaXRpZXNBbGwgJT4lIGRwbHlyOjpmaWx0ZXIoVG90YWxJRCA9PSBncnApDQogIA0KICBpbnZhc2lvbnNEaXJlY3RlZFtbZ3JwXV0gPC0gbWF0cml4KA0KICAgIE5BLCANCiAgICBucm93ID0gbnJvdyhjb21tdW5pdGllc0dycCksDQogICAgbmNvbCA9IG5yb3coY29tbXVuaXRpZXNHcnApDQogICkNCiAgDQogIGZvciAoY2wgaW4gMTpucm93KGNvbW11bml0aWVzR3JwKSkgew0KICAgIGlmIChjb21tdW5pdGllc0dycCRVbmludmFkYWJsZVtjbF0pIHsNCiAgICAgICMgTm8gcG9pbnQgY2hlY2tpbmcsIG1hcmsgRkFMU0UuDQogICAgICBpbnZhc2lvbnNEaXJlY3RlZFtbZ3JwXV1bLCBjbF0gPC0gRkFMU0UNCiAgICB9IGVsc2Ugew0KICAgICAgIyBDaGVjayB0byBzZWUgaWYgYyhvKWwodW1uKSBpcyB1bmludmFkYWJsZSB3aXRoIHJlc3BlY3QgdG8gcm93cy4NCiAgICAgIGZvciAociBpbiAxOm5yb3coY29tbXVuaXRpZXNHcnApKSB7DQogICAgICAgIGlmIChyID09IGNsKSB7aW52YXNpb25zRGlyZWN0ZWRbW2dycF1dW3IsIGNsXSA8LSBGQUxTRTsgbmV4dCgpfQ0KICAgICAgICANCiAgICAgICAgaW52YXNpb25zRGlyZWN0ZWRbW2dycF1dW3IsIGNsXSA8LSB3aXRoKA0KICAgICAgICAgIGNvbW11bml0aWVzR3JwW2NsLCBdLA0KICAgICAgICAgIHsNCiAgICAgICAgICAgIHRlbXBSb3cgPC0gcmVwKE5BLCBucm93KHBvb2xzW1tEYXRhc2V0SURdXVtbQ29tYm5OdW1dXSkgKyAxKQ0KICAgICAgICAgICAgdGVtcElEcyA8LSBSTVRSQ29kZTI6OkNzdlJvd1NwbGl0KENvbW11bml0aWVzKQ0KICAgICAgICAgICAgDQogICAgICAgICAgICB0ZW1wUm93W3RlbXBJRHMgKyAxXSA8LSBSTVRSQ29kZTI6OkNzdlJvd1NwbGl0KENvbW11bml0eUFidW5kKQ0KICAgICAgICAgICAgDQogICAgICAgICAgICAjIEVhc2llc3QgdHJpY2s6IHNldCByZXByb2R1Y3Rpb24gdG8gaW1wb3NzaWJsZSAoLUluZikgZm9yIHNwZWNpZXMgDQogICAgICAgICAgICAjIG5vdCBpbiBlaXRoZXIgdGhlIGludmFkZXJzIG9yIHRoZSBpbnZhZGVkLg0KICAgICAgICAgICAgdGVtcFBvb2wgPC0gcG9vbHNbW0RhdGFzZXRJRF1dW1tDb21ibk51bV1dDQogICAgICAgICAgICB0ZW1wUG9vbCRSZXByb2R1Y3Rpb25SYXRlIDwtIC1JbmYgDQogICAgICAgICAgICB0ZW1wUG9vbCRSZXByb2R1Y3Rpb25SYXRlW3RlbXBJRHNdIDwtIA0KICAgICAgICAgICAgICBwb29sc1tbRGF0YXNldElEXV1bW0NvbWJuTnVtXV0kUmVwcm9kdWN0aW9uUmF0ZVt0ZW1wSURzXQ0KICAgICAgICAgICAgDQogICAgICAgICAgICB0ZW1wSURzIDwtIFJNVFJDb2RlMjo6Q3N2Um93U3BsaXQoY29tbXVuaXRpZXNHcnAkQ29tbXVuaXRpZXNbcl0pDQogICAgICAgICAgICB0ZW1wUG9vbCRSZXByb2R1Y3Rpb25SYXRlW3RlbXBJRHNdIDwtIA0KICAgICAgICAgICAgICBwb29sc1tbRGF0YXNldElEXV1bW0NvbWJuTnVtXV0kUmVwcm9kdWN0aW9uUmF0ZVt0ZW1wSURzXQ0KICAgICAgICAgICAgDQogICAgICAgICAgICAjIFJldHVybiBGQUxTRSBpZiB1bmludmFkYWJsZSwgc2luY2Ugbm8gbmV3IGNvbW11bml0aWVzIGZvcm0uDQogICAgICAgICAgICAhUk1UUkNvZGUyOjpMYXdNb3J0b24xOTk2X0NoZWNrVW5pbnZhZGFibGUoDQogICAgICAgICAgICAgIEFidW5kYW5jZVJvdyA9IHRlbXBSb3csDQogICAgICAgICAgICAgIFBvb2wgPSB0ZW1wUG9vbCwNCiAgICAgICAgICAgICAgQ29tbXVuaXR5TWF0cml4ID0gbWF0c1tbRGF0YXNldElEXV1bW0NvbWJuTnVtXV0NCiAgICAgICAgICAgICkNCiAgICAgICAgICB9DQogICAgICAgICkNCiAgICAgIH0NCiAgICB9DQogIH0NCiAgDQogICMgTm8gVFJVRXMgKD09IHN1Y2Nlc3NmdWwgaW52YXNpb25zKT8gR28gdG8gbmV4dCBzZXQuDQogIGlmICghYW55KGludmFzaW9uc0RpcmVjdGVkW1tncnBdXSkpIHtuZXh0KCl9DQogIA0KICAjIEFueSBUUlVFcyBhcmUgc2l0dWF0aW9ucyBpbiB3aGljaCByb3cgY2FuIGludmFkZSBjb2x1bW4gYW5kIHNob3VsZCBiZSANCiAgIyBjaGVja2VkIGZvciB3aGF0IGNvbW11bml0aWVzIGFwcGVhciBhcyBhIHJlc3VsdC4NCiAgZm9yIChjbCBpbiAxOm5yb3coY29tbXVuaXRpZXNHcnApKSB7DQogICAgaWYgKCFhbnkoaW52YXNpb25zRGlyZWN0ZWRbW2dycF1dWywgY2xdKSkge25leHQoKX0NCiAgICANCiAgICAjVE9ETyBEZXZlbG9wIElzbGFuZEFzc2VtYmx5IGZ1bmN0aW9uLg0KICB9DQp9DQpgYGANCg0KDQojIyBJbmRpcmVjdCBNdXR1YWxpc20gKG9yIENvbXBldGl0aW9uKQ0KSGVyZSwgd2UgY2hlY2sgdG8gc2VlIGlmIHRoZSBuZXR3b3JrcyBjcmVhdGVkIGJ5IGVhY2ggY29tbXVuaXR5IChoeWJyaWQgb3Igb3RoZXJ3aXNlKSBoYXMgbXV0dWFsaXNtIGVtYmVkZGVkIGluIGl0Lg0KDQpUaGUgZmlyc3Qgb2J2aW91cyBzdGVwIGlzIHRvIG1ha2UgYSBnYWxsZXJ5IG9mIHRoZSBmb29kIHdlYnMuDQpUaGUgcmVhZGVyIHdpbGwgbm90aWNlIHRoZSB1cHNpZGUtZG93biAnVCcgc2hhcGUgdG8gdGhlIHBsb3RzLg0KPCEtLVdlIHdpbGwgdXNlIGEgbmVhdCB0cmljayBmcm9tIGBodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL2EvNTM0NDQ2NTRgLg0KYGBge3IgbmVhdFRyaWNrfQ0KY2F0SGVhZGVyIDwtIGZ1bmN0aW9uKHRleHQgPSAiIiwgbGV2ZWwgPSAzKSB7DQogICAgY2F0KHBhc3RlMCgiXG5cbiIsIA0KICAgICAgICAgICAgICAgcGFzdGUocmVwKCIjIiwgbGV2ZWwpLCBjb2xsYXBzZSA9ICIiKSwgDQogICAgICAgICAgICAgICAiICIsIHRleHQsICJcbiIpKQ0KfQ0KYGBgDQotLT4NCg0KV2Ugd2lsbCBhbHNvIG5lZWQgdG8gcmVjcmVhdGUgY29kZSBmcm9tIHRoZSBmaWxlIGBMYXdNb3J0b24xOTk2LU51bWVyaWNhbFRhYmxlcy1QYXJhbGxlbC5SbWRgLg0KYGBge3IgY3JlYXRlR3JhcGhzfQ0KZm9vZFdlYnMgPC0gbGlzdCgpDQoNCmZvciAociBpbiAxOm5yb3coY29tbXVuaXRpZXNBbGwpKSB7DQogIGZvb2RXZWJzW1tyXV0gPC0gd2l0aCgNCiAgICBjb21tdW5pdGllc0FsbFtyLCBdLA0KICAgIHsNCiAgICAgIHJlZENvbSA8LSBSTVRSQ29kZTI6OkNzdlJvd1NwbGl0KENvbW11bml0aWVzKQ0KICAgICAgcmVkTWF0IDwtIG1hdHNbW0RhdGFzZXRJRF1dW1tDb21ibk51bV1dW3JlZENvbSwgcmVkQ29tXQ0KICAgICAgcmVkUG9vbCA8LSBwb29sc1tbRGF0YXNldElEXV1bW0NvbWJuTnVtXV1bcmVkQ29tLCBdDQogICAgICANCiAgICAgIGNvbG5hbWVzKHJlZE1hdCkgPC0gcGFzdGUwKCdzJyxhcy5jaGFyYWN0ZXIocmVkQ29tKSkNCiAgICAgIHJvd25hbWVzKHJlZE1hdCkgPC0gY29sbmFtZXMocmVkTWF0KQ0KICAgICAgDQogICAgICBuYW1lcyhyZWRQb29sKVsxXSA8LSAibm9kZSINCiAgICAgIHJlZFBvb2wkbm9kZSA8LSBjb2xuYW1lcyhyZWRNYXQpDQogICAgICBuYW1lcyhyZWRQb29sKVszXSA8LSAiTSINCiAgICAgIA0KICAgICAgR3JhcGggPC0gaWdyYXBoOjpncmFwaF9mcm9tX2FkamFjZW5jeV9tYXRyaXgoDQogICAgICAgIHJlZE1hdCwgd2VpZ2h0ZWQgPSBUUlVFDQogICAgICApDQogICAgICANCiAgICAgIEdyYXBoIDwtIGlncmFwaDo6c2V0LnZlcnRleC5hdHRyaWJ1dGUoDQogICAgICAgIEdyYXBoLCAibmFtZSIsIHZhbHVlID0gY29sbmFtZXMocmVkTWF0KQ0KICAgICAgKQ0KICAgICAgDQogICAgICByZWRQb29sJE4gPC0gUk1UUkNvZGUyOjpDc3ZSb3dTcGxpdChDb21tdW5pdHlBYnVuZCkNCiAgICAgIA0KICAgICAgR3JhcGhBc0RhdGFGcmFtZSA8LSBpZ3JhcGg6OmFzX2RhdGFfZnJhbWUoR3JhcGgpDQogICAgDQogICAgICAjIGNoZWRkYXIgZG9lcyBub3QgbGlrZSBjYW5uaWJhbHMuDQogICAgICBHcmFwaEFzRGF0YUZyYW1lIDwtIEdyYXBoQXNEYXRhRnJhbWVbDQogICAgICAgIEdyYXBoQXNEYXRhRnJhbWUkdG8gIT0gR3JhcGhBc0RhdGFGcmFtZSRmcm9tLA0KICAgICAgXQ0KICANCiAgICAgICMgQWRkIGluIGFidW5kYW5jZXMgZm9yIGNhbGN1bGF0aW5nIGFidW5kYW5jZSAqIChnYWluIG9yIGxvc3MpDQogICAgICBHcmFwaEFzRGF0YUZyYW1lIDwtIGRwbHlyOjpsZWZ0X2pvaW4oDQogICAgICAgIEdyYXBoQXNEYXRhRnJhbWUsDQogICAgICAgIGRwbHlyOjpzZWxlY3QocmVkUG9vbCwgbm9kZSwgTiksDQogICAgICAgIGJ5ID0gYygidG8iID0gIm5vZGUiKQ0KICAgICAgKQ0KICANCiAgICAgICMgU3BsaXQgZGF0YSBmcmFtZS4NCiAgICAgIFJlc0NvbiA8LSBHcmFwaEFzRGF0YUZyYW1lW0dyYXBoQXNEYXRhRnJhbWUkd2VpZ2h0ID4gMCxdDQogICAgICBDb25SZXMgPC0gR3JhcGhBc0RhdGFGcmFtZVtHcmFwaEFzRGF0YUZyYW1lJHdlaWdodCA8IDAsXQ0KICAgICAgDQogICAgICAjIFJlb3JkZXIgYW5kIHJlbmFtZSB2YXJpYWJsZXMuDQogICAgICBSZXNDb24gPC0gZHBseXI6OnNlbGVjdChSZXNDb24sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVzb3VyY2UgPSB0bywgY29uc3VtZXIgPSBmcm9tLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdhaW5QZXJVbml0ID0gd2VpZ2h0LCByZXNvdXJjZUFidW5kID0gTikNCiAgICAgIENvblJlcyA8LSBkcGx5cjo6c2VsZWN0KENvblJlcywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXNvdXJjZSA9IGZyb20sIGNvbnN1bWVyID0gdG8sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbG9zc1BlclVuaXQgPSB3ZWlnaHQsIGNvbnN1bWVyQWJ1bmQgPSBOKQ0KICAgICAgDQogICAgICBSZXNDb24gPC0gZHBseXI6Om11dGF0ZShkcGx5cjo6Z3JvdXBfYnkoUmVzQ29uLCBjb25zdW1lciksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnYWluRWZmaWNpZW5jeSA9IGdhaW5QZXJVbml0IC8gc3VtKGdhaW5QZXJVbml0KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdhaW5BY3R1YWwgPSBnYWluUGVyVW5pdCAqIHJlc291cmNlQWJ1bmQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnYWluTm9ybWFsID0gZ2FpbkFjdHVhbCAvIHN1bShnYWluQWN0dWFsKSkNCiAgICAgIENvblJlcyA8LSBkcGx5cjo6bXV0YXRlKGRwbHlyOjpncm91cF9ieShDb25SZXMsIHJlc291cmNlKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxvc3NFZmZpY2llbmN5ID0gbG9zc1BlclVuaXQgLyBzdW0obG9zc1BlclVuaXQpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbG9zc0FjdHVhbCA9IGxvc3NQZXJVbml0ICogY29uc3VtZXJBYnVuZCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxvc3NOb3JtYWwgPSBsb3NzQWN0dWFsIC8gc3VtKGxvc3NBY3R1YWwpKQ0KICAgICAgDQogICAgICBjaGVkZGFyQ29tbXVuaXR5IDwtIGNoZWRkYXI6OkNvbW11bml0eSgNCiAgICAgICAgcmVkUG9vbCwNCiAgICAgICAgcHJvcGVydGllcyA9IGxpc3QoDQogICAgICAgICAgdGl0bGUgPSBwYXN0ZShUb3RhbElELCAiOiIsIENvbW11bml0aWVzLCAiOiByb3ciLCByKSwNCiAgICAgICAgICBNLnVuaXRzID0gIm1hc3NlcyIsDQogICAgICAgICAgTi51bml0cyA9ICJhYnVuZCINCiAgICAgICAgKSwNCiAgICAgICAgdHJvcGhpYy5saW5rcyA9IGRwbHlyOjpmdWxsX2pvaW4oUmVzQ29uLCBDb25SZXMsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9IGMoInJlc291cmNlIiwgImNvbnN1bWVyIikpDQogICAgICApDQogICAgICANCiAgICAgIGNoZWRkYXJDb21tdW5pdHkNCiAgICB9DQogICkNCn0NCmBgYA0KDQojIyMgRXhhbXBsZSBHYWxsZXJ5IHsudGFic2V0fQ0KPCEtLWBgYHtyIHRlbXBsb3QsIHJlc3VsdHMgPSAiYXNpcyIsIGVjaG8gPSBGQUxTRX0NCmZvciAoaSBpbiBzZXFfYWxvbmcoZm9vZFdlYnMpWzE6NV0pIHsNCiAgdG1wIDwtIGZvb2RXZWJzW1tpXV0NCiAgY2F0SGVhZGVyKGksIDQpDQogIHByaW50KGNoZWRkYXI6OlBsb3RXZWJCeUxldmVsKHRtcCwgc2hvdy5sZXZlbC5saW5lcyA9IFRSVUUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbCA9ICJMb25nV2VpZ2h0ZWRUcm9waGljTGV2ZWwiKSkNCn0NCmBgYC0tPg0KDQojIyMjIENsb3NlZA0KDQojIyMjIEV4YW1wbGUgTE0gMQ0KYGBge3IgZ2FsbGVyeTF9DQpwcmludChjaGVkZGFyOjpQbG90V2ViQnlMZXZlbChmb29kV2Vic1tbMV1dLCBzaG93LmxldmVsLmxpbmVzID0gVFJVRSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVsID0gIkxvbmdXZWlnaHRlZFRyb3BoaWNMZXZlbCIpKQ0KYGBgDQoNCiMjIyMgRXhhbXBsZSBMTSAyDQpgYGB7ciBnYWxsZXJ5Mn0NCnByaW50KGNoZWRkYXI6OlBsb3RXZWJCeUxldmVsKGZvb2RXZWJzW1syOF1dLCBzaG93LmxldmVsLmxpbmVzID0gVFJVRSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVsID0gIkxvbmdXZWlnaHRlZFRyb3BoaWNMZXZlbCIpKQ0KYGBgDQoNCiMjIyMgSW52YWRhYmxlDQpgYGB7ciBnYWxsZXJ5M30NCnByaW50KGNoZWRkYXI6OlBsb3RXZWJCeUxldmVsKGZvb2RXZWJzW1s0MV1dLCBzaG93LmxldmVsLmxpbmVzID0gVFJVRSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVsID0gIkxvbmdXZWlnaHRlZFRyb3BoaWNMZXZlbCIpKQ0KYGBgDQoNCiMjIyMgRG9lcyBOb3QgUGVyc2lzdA0KYGBge3IgZ2FsbGVyeTR9DQpwcmludChjaGVkZGFyOjpQbG90V2ViQnlMZXZlbChmb29kV2Vic1tbNjFdXSwgc2hvdy5sZXZlbC5saW5lcyA9IFRSVUUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbCA9ICJMb25nV2VpZ2h0ZWRUcm9waGljTGV2ZWwiKSkNCmBgYA0KDQojIyMjIEh5YnJpZA0KYGBge3IgZ2FsbGVyeTV9DQpwcmludChjaGVkZGFyOjpQbG90V2ViQnlMZXZlbChmb29kV2Vic1tbODFdXSwgc2hvdy5sZXZlbC5saW5lcyA9IFRSVUUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbCA9ICJMb25nV2VpZ2h0ZWRUcm9waGljTGV2ZWwiKSkNCmBgYA0KDQojIyMgTWVhc3VyaW5nIEluZGlyZWN0IEludGVyYWN0aW9ucw0KUGVyaGFwcyB0aGUgbW9zdCBvYnZpb3VzIHdheSB0byBtZWFzdXJlIGluZGlyZWN0IGVmZmVjdHMgb2Ygb25lIG5vZGUgb24gYW5vdGhlciBpcyB0byBjb25zaWRlciB0aGUgbWF0cml4IHBvd2VyLg0KVGhlIGVudHJpZXMgaW4gdGhlIGZpcnN0IHBvd2VyICRNXjEkIHJlcHJlc2VudCB0aGUgZGlyZWN0ICh1bi1ub3JtYWxpc2VkKSBlZmZlY3RzIG9mIHNwZWNpZXMgJGokIChjb2x1bW4pIG9uIHNwZWNpZXMgJGkkIChyb3cpLg0KKE11bHRpcGx5IHRoZSBpbnRlcmFjdGlvbnMgYnkgdGhlIGFidW5kYW5jZSBjb2x1bW4gdmVjdG9yIG9uIHRoZSByaWdodCB0byBzZWUgd2h5IEkgdXNlIHRoaXMgY29udmVudGlvbi4pDQpUaGVuIHRoZSBlbnRyaWVzIG9mICRNXm4kIHJlcHJlc2VudCB0aGUgZWZmZWN0cyBvZiBzcGVjaWVzICRqJCBvbiBzcGVjaWVzICRpJCBhZnRlciBhIHBhdGggb2YgZXhhY3RseSAkbiQgc3RlcHMuDQpbaHR0cHM6Ly9kb2kub3JnLzEwLjEwMTYvai5lY29jb20uMjAwNy4wNS4wMDJdKFNjb3R0aSBldCBhbC4gMjAwNykgYW5kDQpbaHR0cHM6Ly9kb2kub3JnLzEwLjExMTEvZWxlLjEyNjM4XShaaGFvIGV0IGFsLiAyMDE2KSBib3RoIHJlY29tbWVuZCBlc3NlbnRpYWxseSB0byBub3JtYWxpc2UgdGhpcyBzY29yZSBhbmQgc3VtIGl0IGFjcm9zcyB0aGUgZmlyc3Qgc28gbWFueSAoMyBhbmQgNSByZXNwZWN0aXZlbHkpIHN0ZXBzLg0KVGhlIGxhdHRlciB1c2VzIGl0IGZvciBgcXVhbGl0YXRpdmUgZmVlZGluZ2AgbWF0cmljZXMsIHdoaWxlIHRoZSBmb3JtZXIgc3VnZ2VzdHMgYmlvbWFzcyBmbG93IHJhdGhlciB0aGFuIHRoZSBpbnRlcmFjdGlvbiBtYXRyaWNlcyB3ZSBhcmUgdXNpbmcgSSBiZWxpZXZlLg0KDQpTb21lIG5vdGVzIGJlZm9yZSB3ZSBiZWdpbiB3aXRoIHRoaXMuDQpUaGUgdW5pdHMgYXJlIGEgYml0IHdvbmt5IGlmIHdlIGFyZSBub3QgcGF5aW5nIGF0dGVudGlvbjsgdGhlIGludGVyYWN0aW9uIG1hdHJpeCBpdHNlbGYgYmVmb3JlIG11bHRpcGx5aW5nIGJ5IGFidW5kYW5jZSBoYXMgdW5pdHMgaW52ZXJzZSB0aW1lLWRlbnNpdHkuIA0KU28gaW5zdGVhZCBvZiB0YWtpbmcgdGhlIGludGVyYWN0aW9uIG1hdHJpeCAkQSQgZGlyZWN0bHksIHdlIHdpbGwgaW5zdGVhZCB0YWtlICRCOmJfe2ksan0gPSBhX3tpLCBqfSB4X2ogcyQgd2hlcmUgJHgkIGlzIGFuIGFidW5kYW5jZSAoaS5lLiBkZW5zaXR5KSBhbmQgJHMkIHJlcHJlc2VudHMgYSB0aW1lIHVuaXQuDQpUaGlzIGlzIGEgYml0IHN0cmFuZ2UsIHNpbmNlIEkgYW0gbm90IGRvaW5nIHRoZSBvYnZpb3VzIHZlY3RvciBvcGVyYXRpb24gYXMgSSB3YW50IHRvIHByZXNlcnZlIHRoZSBkaW1lbnNpb25hbGl0eS4NClRoaXMgY2FuIGJlIHRob3VnaHQgb2YgYXMgaW50ZWdyYXRpbmcgdGhlIG1hdHJpeCBmb3Igb25lIHRpbWUgdW5pdCBpbnN0ZWFkIHRvIHJlbW92ZSB0aGF0IGRpbWVuc2lvbiwgYnV0IHRoaXMgbWFrZXMgdGhlIHJlc3VsdCBpbnZhbGlkIGlmIHRoZSBzeXN0ZW0gaXMgbm90IGluIGEgc3RlYWR5LXN0YXRlIChhcyB0aGUgc3lzdGVtIHdvdWxkIHRoZW4gaGF2ZSBhIHRpbWUgZGVwZW5kZW5jZSByYXRoZXIgdGhhbiBhIGNvbnN0YW50IGludGVncmFsKS4NCg0KTmV4dCwgaXQgaXMgbm90IGltbWVkaWF0ZWx5IG9idmlvdXMgKHRvIG1lIGF0IGxlYXN0KSB3aGF0IHRoZSBjb3JyZWN0IHdheSB0byBtZWFzdXJlIHRoZSBpbmZsdWVuY2Ugb2Ygb25lIHNwZWNpZXMgb24gYW5vdGhlciBpcy4NCkkuZS4gc2hvdWxkIG9uZSB0YWtlICRcc3VtX3tpID0gMX1ee259IE1ebiQ/IFNob3VsZCB0aGVyZSBiZSBwZW5hbHRpZXMgd2l0aCBkaXN0YW5jZT8NCg0KSW5kZWVkLCBob3cgZG8gd2UgY29tcGFyZSB0aGUgZWZmZWN0cyAoZGlyZWN0IG9yIGluZGlyZWN0KSB3aXRoIHRoZWlyIGluZmx1ZW5jZSBvbiB0aGUgc3lzdGVtIGl0c2VsZj8gVGhhdCBpcywgd2UgY2FuIGNlcnRhaW5seSBjYWxjdWxhdGUgc29tZXRoaW5nLCBidXQgaG93IGNhbiB3ZSBiZSBjZXJ0YWluIHRoYXQgd2hhdCB3ZSB0aGluayB3ZSBhcmUgY2FsY3VsYXRpbmcgYW5kIHdoYXQgd2UgYXJlIGFjdHVhbGx5IGNhbGN1bGF0aW5nIGFyZSB0aGUgc2FtZSB0aGluZz8NCkZvciBleGFtcGxlLCB3ZSB3b3VsZCBleHBlY3QgZGlyZWN0IGFuZCBpbmRpcmVjdCBlZmZlY3RzIHRvIGJlIHByZXNlbnQgYXMgZGV2aWF0aW9ucyBmcm9tIHRoZSBzdGVhZHktc3RhdGUgYXJlIHJlc29sdmVkLCBidXQgaG93IGRvIHdlIGV4dHJhY3QgdGhlIGluZGlyZWN0IGVmZmVjdHMgYW5kIGNvbXBhcmUgd2l0aCwgZS5nLiwgdGhlIG1hdHJpeCBwb3dlcnM/DQoNCjwhLS0NCk9uZSB3YXkgdG8gY29tcGFyZSB3b3VsZCBiZSB0byB0YWtlIHRoZSBpbnRlZ3JhbCBvZiB0aGUgZGV2aWF0aW9ucyBmcm9tIHRoZSBzdGVhZHktc3RhdGUgdXNpbmcgYSBwZXJ0dXJiYXRpb24gYXBwbGllZCBzZXF1ZW50aWFsbHkgdG8gZWFjaCBzcGVjaWVzLg0KRm9yIG5vdywgbGV0IHVzIGp1c3QgYXBwbHkgdGhlIGZpcnN0IGZldyBwb3dlcnMgYW5kIHNlZSBpZiB0aGF0IHlpZWxkcyBuZXcgaW5mb3JtYXRpb24uDQpXZSBjYW4gY29udGludWUgZG93biBvdGhlciBwYXRocyBpZiBpdCBpcyBpbnRlcmVzdGluZyBvciB3ZSBkbyBub3Qgc2VlIGFueXRoaW5nIGVtZXJnZS4NCi0tPg0KDQpgYGB7ciBwZXJ0dXJiYXRpb25FZmZlY3RzfQ0KIyBGb3IgZWFjaCBjb21tdW5pdHkgdGhhdCBwZXJzaXN0cy9yZXR1cm5zIHRvIHN0ZWFkeS1zdGF0ZS4uLg0KIyBSdW4gdGhlIGR5bmFtaWNzIHdpdGggYSBwZXJ0dXJiYXRpb24gZm9yIGVhY2ggc3BlY2llcyBpbiB0aGUgY29tbXVuaXR5Li4uDQojICJJbnRlZ3JhdGUiIChsYXp5IFJpZW1hbm4gc3VtKSB0aGUgZHluYW1pY3MgdG8gZ2V0IGEgdG90YWwgZWZmZWN0IG92ZXIgdGltZQ0KIyAgIGFzIHRoZSBzeXN0ZW0gY29sbGFwc2VzIGJhY2sgdG8gc3RlYWR5LXN0YXRlLi4uDQojIENyZWF0ZSBhIG1hdHJpeCBvZiB0aGUgZWZmZWN0cywgd2hpY2ggY29udGFpbiB0aGUgdG90YWwgZWZmZWN0cyBkdWUgdG8gYSANCiMgICBwZXJ0dXJiYXRpb24gb3ZlciB0aW1lLg0KIyBCb251czogY29ycmVsYXRlIHdpdGggdGhlIEZpcnN0LCBTZWNvbmQsIFRoaXJkLCBhbmQgc3VtIG9mIE1hdHJpeCBQb3dlcnM/DQojIChIaWdoIGNvcnJlbGF0aW9uIG1lYW5zIHRoYXQgdGhlIG1hdHJpeCBwb3dlcnMgZG8gYWN0dWFsbHkgbWVhc3VyZSB0aGUgZWZmZWN0cw0KIyAgb2YgcGVydHVyYmF0aW9ucyB0byBhIHBvcHVsYXRpb24gZnJvbSB0aGUgc3RlYWR5LXN0YXRlLikNCm1hdHNQZXJ0dXJiYXRpb24gPC0gbGlzdCgpDQptYXRzRWZmZWN0czEgPC0gbGlzdCgpDQptYXRzRWZmZWN0czIgPC0gbGlzdCgpDQptYXRzRWZmZWN0czMgPC0gbGlzdCgpDQptYXRzRWZmZWN0c0FkZCA8LSBsaXN0KCkNCmZvciAoaSBpbiAxOm5yb3coY29tbXVuaXRpZXNBbGwpKSB7DQogIGNvbW11bml0eVRoZSA8LSBjb21tdW5pdGllc0FsbFtpLCBdDQogIGlmICghY29tbXVuaXR5VGhlJFBlcnNpc3RzKSB7bmV4dH0NCiAgDQogIG1hdHNQZXJ0dXJiYXRpb25bW2ldXSA8LSBtYXRyaXgoTkEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3cgPSBjb21tdW5pdHlUaGUkQ29tbXVuaXR5U2l6ZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuY29sID0gY29tbXVuaXR5VGhlJENvbW11bml0eVNpemUpDQogIA0KICAjIEVhY2ggZW50cnkgaXMgdGhlIGVmZmVjdCBvZiB0aGUgY29sdW1uIG9uIHRoZSByb3cuDQogICMgSGVuY2UsIHdlIHdpbGwgYmUgcGxhY2luZyBjb2x1bW4gdmVjdG9ycyBpbiB0aGUgbWF0cml4Lg0KICAjVE9ETyBOb3RlIHRvIGZ1dHVyZSBtZTogZG91YmxlIGNoZWNrIHRoZSB0cmFuc3Bvc2UsIGp1c3QgaW4gY2FzZS4NCiAgDQogIGZvciAociBpbiAxOmNvbW11bml0eVRoZSRDb21tdW5pdHlTaXplKSB7DQogICAgbWF0c1BlcnR1cmJhdGlvbltbaV1dWywgcl0gPC0gd2l0aCgNCiAgICAgIGNvbW11bml0eVRoZSwgDQogICAgICB7DQogICAgICAgIHRlbXBDb21tdW5pdHkgPC0gUk1UUkNvZGUyOjpDc3ZSb3dTcGxpdChDb21tdW5pdGllcykNCiAgICAgICAgcGVydHVyYmF0aW9uIDwtIHJlcCgwLCBDb21tdW5pdHlTaXplKQ0KICAgICAgICBhYnVuZCA8LSBSTVRSQ29kZTI6OkNzdlJvd1NwbGl0KENvbW11bml0eUFidW5kKQ0KICAgICAgICBwZXJ0dXJiYXRpb25bcl0gPC0gMSMwLjAwMDEgKiBhYnVuZFtyXQ0KICAgICAgICBkeW5hbWljcyA8LSBSTVRSQ29kZTI6Okxhd01vcnRvbjE5OTZfTnVtSW50ZWdyYXRpb24oDQogICAgICAgICAgQSA9IG1hdHNbW0RhdGFzZXRJRF1dW1tDb21ibk51bV1dW3RlbXBDb21tdW5pdHksIHRlbXBDb21tdW5pdHldLA0KICAgICAgICAgIFIgPSBwb29sc1tbRGF0YXNldElEXV1bW0NvbWJuTnVtXV0kUmVwcm9kdWN0aW9uUmF0ZVt0ZW1wQ29tbXVuaXR5XSwNCiAgICAgICAgICBYID0gYWJ1bmQgKyBwZXJ0dXJiYXRpb24sDQogICAgICAgICAgT3V0ZXJUaW1lU3RlcFNpemUgPSAxLA0KICAgICAgICAgIElubmVyVGltZVN0ZXBTaXplID0gMC4wMDEsDQogICAgICAgICAgVG9sZXJhbmNlID0gMUUtNg0KICAgICAgICApICMgQ29sdW1uOiBTcGVjaWVzLCBSb3c6IFRpbWUNCiAgICAgICAgdGltZWRpZmYgPC0gZGlmZihkeW5hbWljc1ssIDFdKQ0KICAgICAgICBkeW5hbWljcyA8LSBkeW5hbWljc1ssIC0xXQ0KICAgICAgICB1bmxpc3QobGFwcGx5KDE6bmNvbChkeW5hbWljcyksIEZVTiA9IGZ1bmN0aW9uKG5jLCB4LCB4MCwgdCkgew0KICAgICAgICAgIHN1bSgoeFstMSwgbmNdIC0geDBbbmNdKSAqIHRpbWVkaWZmKQ0KICAgICAgICB9LCB4ID0gZHluYW1pY3MsIHgwID0gYWJ1bmQsIHQgPSB0aW1lZGlmZikpDQogICAgICB9DQogICAgKQ0KICB9DQogIA0KICAjIENvbXB1dGUgbWF0c0VmZmVjdHNebg0KICBtYXRzRWZmZWN0czFbW2ldXSA8LSB3aXRoKA0KICAgIGNvbW11bml0eVRoZSwgDQogICAgICB7DQogICAgICAgIHRlbXBDb21tdW5pdHkgPC0gUk1UUkNvZGUyOjpDc3ZSb3dTcGxpdChDb21tdW5pdGllcykNCiAgICAgICAgYWJ1bmQgPC0gUk1UUkNvZGUyOjpDc3ZSb3dTcGxpdChDb21tdW5pdHlBYnVuZCkNCiAgICAgICAgdGVtcG1hdCA8LSBtYXRzW1tEYXRhc2V0SURdXVtbQ29tYm5OdW1dXVt0ZW1wQ29tbXVuaXR5LCB0ZW1wQ29tbXVuaXR5XQ0KICAgICAgICBkby5jYWxsKGNiaW5kLCBsYXBwbHkoMTpuY29sKHRlbXBtYXQpLCBGVU4gPSBmdW5jdGlvbihuYywgeCwgeDApIHsNCiAgICAgICAgICAoeFssIG5jXSAqIHgwW25jXSkNCiAgICAgICAgfSwgeCA9IHRlbXBtYXQsIHgwID0gYWJ1bmQpKQ0KICAgICAgfQ0KICApDQogIG1hdHNFZmZlY3RzMltbaV1dIDwtIGV4cG06OmAlXiVgKG1hdHNFZmZlY3RzMVtbaV1dLCAyKQ0KICBtYXRzRWZmZWN0czNbW2ldXSA8LSBleHBtOjpgJV4lYChtYXRzRWZmZWN0czFbW2ldXSwgMykNCiAgbWF0c0VmZmVjdHNBZGRbW2ldXSA8LSANCiAgICBtYXRzRWZmZWN0czFbW2ldXSArIG1hdHNFZmZlY3RzMltbaV1dICsgbWF0c0VmZmVjdHMzW1tpXV0NCn0NCmBgYA0KDQpgYGB7cn0NCiMgQXZlcmFnZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIG1hdHJpeCBlbnRyaWVzIGFjcm9zcyBhbGwgbWF0cmljZXMNCnByaW50KCJQZXJ0dXJiYXRpb24gdnMgMXN0IFBvd2VyOiIpDQptZWFuKHVubGlzdChsYXBwbHkoc2VxX2Fsb25nKG1hdHNQZXJ0dXJiYXRpb24pLCBmdW5jdGlvbihpLCBtMSwgbTIpIHsNCiAgaWYgKGlzLm51bGwobTFbW2ldXSkpIHJldHVybihOVUxMKQ0KICBjb3IobTFbW2ldXVsxOihucm93KG0xW1tpXV0pXjIpXSwNCiAgICAgIG0yW1tpXV1bMToobnJvdyhtMltbaV1dKV4yKV0pDQp9LCBtMSA9IG1hdHNQZXJ0dXJiYXRpb24sIG0yID0gbWF0c0VmZmVjdHMxKSkpDQpwcmludCgiUGVydHVyYmF0aW9uIHZzIDJuZCBQb3dlcjoiKQ0KbWVhbih1bmxpc3QobGFwcGx5KHNlcV9hbG9uZyhtYXRzUGVydHVyYmF0aW9uKSwgZnVuY3Rpb24oaSwgbTEsIG0yKSB7DQogIGlmIChpcy5udWxsKG0xW1tpXV0pKSByZXR1cm4oTlVMTCkNCiAgY29yKG0xW1tpXV1bMToobnJvdyhtMVtbaV1dKV4yKV0sDQogICAgICBtMltbaV1dWzE6KG5yb3cobTJbW2ldXSleMildKQ0KfSwgbTEgPSBtYXRzUGVydHVyYmF0aW9uLCBtMiA9IG1hdHNFZmZlY3RzMikpKQ0KcHJpbnQoIlBlcnR1cmJhdGlvbiB2cyAzcmQgUG93ZXI6IikNCm1lYW4odW5saXN0KGxhcHBseShzZXFfYWxvbmcobWF0c1BlcnR1cmJhdGlvbiksIGZ1bmN0aW9uKGksIG0xLCBtMikgew0KICBpZiAoaXMubnVsbChtMVtbaV1dKSkgcmV0dXJuKE5VTEwpDQogIGNvcihtMVtbaV1dWzE6KG5yb3cobTFbW2ldXSleMildLA0KICAgICAgbTJbW2ldXVsxOihucm93KG0yW1tpXV0pXjIpXSkNCn0sIG0xID0gbWF0c1BlcnR1cmJhdGlvbiwgbTIgPSBtYXRzRWZmZWN0czMpKSkNCnByaW50KCJQZXJ0dXJiYXRpb24gdnMgU3VtIG9mIDE6MyBwb3dlcnM6IikNCm1lYW4odW5saXN0KGxhcHBseShzZXFfYWxvbmcobWF0c1BlcnR1cmJhdGlvbiksIGZ1bmN0aW9uKGksIG0xLCBtMikgew0KICBpZiAoaXMubnVsbChtMVtbaV1dKSkgcmV0dXJuKE5VTEwpDQogIGNvcihtMVtbaV1dWzE6KG5yb3cobTFbW2ldXSleMildLA0KICAgICAgbTJbW2ldXVsxOihucm93KG0yW1tpXV0pXjIpXSkNCn0sIG0xID0gbWF0c1BlcnR1cmJhdGlvbiwgbTIgPSBtYXRzRWZmZWN0c0FkZCkpKQ0KYGBgDQpUaGlzIHR1cm5zIG91dCB0byBiZSBmYWlybHkgc2Vuc2l0aXZlIHRvIHRoZSB0aW1lc2NhbGUgY29uc2lkZXJlZCAoMSB0aW1lIHVuaXQgdmVyc3VzIDEwMCBmb3IgaW5zdGFuY2UpLCBidXQgbm90IG9idmlvdXNseSBzbyBmb3IgdGhlIChhYnNvbHV0ZSByYXRoZXIgdGhhbiByZWxhdGl2ZSkgcGVydHVyYmF0aW9uIHNpemUgKDEgdnMgMC4xIG9yIDAuMDEpLg0KQ2hhbmdpbmcgZnJvbSBhYnNvbHV0ZSB0byByZWxhdGl2ZSBncmVhdGx5IHJlZHVjZXMgdGhlIGNvcnJlbGF0aW9uIHRvIHZhbHVlcyBiZXR3ZWVuIC0wLjIgYW5kIC0wLjA1IHJvdWdobHkgZm9yIHZhbHVlcyBvZiAwLjAxLCAwLjAwMSwgYW5kIDAuMDAwMS4NCg0KQXMgZm9yIGhvdyB3ZSBjYW4gdXNlIHRoZSBtYXRyaXgsIG9uZSBlYXN5IHNldCBvZiBzdW1tYXJ5IHN0YXRpc3RpY3MgaXMgdG8gbG9vayBmb3IgdGhlIHByb3BvcnRpb25zIG9mIHZhcmlvdXMgcmVsYXRpb25zaGlwIHR5cGVzLg0KYGBge3J9DQptYXRzUGVydHVyYmF0aW9uc1Byb3BzIDwtIGRvLmNhbGwocmJpbmQsIGxhcHBseShtYXRzUGVydHVyYmF0aW9uLCBmdW5jdGlvbihtKSB7DQogIGlmIChpcy5udWxsKG0pKSByZXR1cm4oDQogICAgZGF0YS5mcmFtZSgNCiAgICAgIFNlbGZSZWd1bGF0aW9uUG9zID0gTkEsDQogICAgICBTZWxmUmVndWxhdGlvbk5lZyA9IE5BLA0KICAgICAgTXV0dWFsaXNtID0gTkEsDQogICAgICBDb21wZXRpdGlvbiA9IE5BLA0KICAgICAgRXhwbG9pdGF0aW9uID0gTkENCiAgICApDQogICkNCiAgDQogIG11dHVhbCA8LSAwDQogIGNvbXBldCA8LSAwDQogIGV4cGxvaSA8LSAwDQogIGludGVycyA8LSAwDQogIGZvciAoaSBpbiAxOihucm93KG0pIC0gMSkpIHsNCiAgICBmb3IgKGogaW4gKGkrMSk6KG5jb2wobSkpKSB7DQogICAgICBpZiAobVtpLCBqXSA+IDAgJiYgbVtqLCBpXSA+IDApICAgICAgbXV0dWFsIDwtIG11dHVhbCArIDENCiAgICAgIGVsc2UgaWYgKG1baSwgal0gPCAwICYmIG1baiwgaV0gPCAwKSBjb21wZXQgPC0gY29tcGV0ICsgMQ0KICAgICAgZWxzZSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV4cGxvaSA8LSBleHBsb2kgKyAxDQogICAgICBpbnRlcnMgPC0gaW50ZXJzICsgMSAjIGV4cGVjdGluZyAobnJvdyhtKSAqIChucm93KG0pIC0gMSkgLyAyKQ0KICAgIH0NCiAgfQ0KICANCiAgZGF0YS5mcmFtZSgNCiAgICBTZWxmUmVndWxhdGlvblBvcyA9IHN1bShkaWFnKG0pID4gMCkgLyBucm93KG0pLA0KICAgIFNlbGZSZWd1bGF0aW9uTmVnID0gc3VtKGRpYWcobSkgPCAwKSAvIG5yb3cobSksDQogICAgTXV0dWFsaXNtID0gbXV0dWFsIC8gaW50ZXJzLA0KICAgIENvbXBldGl0aW9uID0gY29tcGV0IC8gaW50ZXJzLA0KICAgIEV4cGxvaXRhdGlvbiA9IGV4cGxvaSAvIGludGVycw0KICApDQp9KSkNCmBgYA0KDQpgYGB7cn0NCmNiaW5kKGNvbW11bml0aWVzQWxsLCBtYXRzUGVydHVyYmF0aW9uc1Byb3BzKWwNCmBgYA0K